├── .eslintignore ├── .eslintrc.js ├── .github ├── CODEOWNERS └── workflows │ ├── deprecate.yml │ ├── e2e_pr.yml │ ├── release-edge.yml │ ├── release.yml │ └── validate.yml ├── .gitignore ├── .husky └── commit-msg ├── .npmrc ├── .prettierrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── babel.config.js ├── commitlint.config.js ├── docs ├── 360.html ├── _template.html ├── adaptive-streaming.html ├── analytics.html ├── api.html ├── audio.html ├── autoplay-on-scroll.html ├── chapters.html ├── cloudinary-analytics.html ├── codec-formats.html ├── colors.html ├── components.html ├── custom-cld-errors.html ├── debug.html ├── embedded-iframe.html ├── es-modules │ ├── 360.html │ ├── _template.html │ ├── adaptive-streaming.html │ ├── all.html │ ├── analytics.html │ ├── api.html │ ├── audio.html │ ├── autoplay-on-scroll.html │ ├── chapters.html │ ├── cloudinary-analytics.html │ ├── codec-formats.html │ ├── colors.html │ ├── components.html │ ├── custom-cld-errors.html │ ├── debug.html │ ├── floating-player.html │ ├── fluid.html │ ├── force-hls-subtitles.html │ ├── highlights-graph.html │ ├── index.html │ ├── interaction-area.html │ ├── multiple-players.html │ ├── netlify.toml │ ├── package-lock.json │ ├── package.json │ ├── playlist-by-tag.html │ ├── playlist.html │ ├── pnpm-lock.yaml │ ├── poster.html │ ├── profiles.html │ ├── raw-url.html │ ├── recommendations.html │ ├── seek-thumbs.html │ ├── shoppable.html │ ├── subtitles-and-captions.html │ ├── transformations.html │ ├── ui-config.html │ ├── vast-vpaid.html │ ├── visual-search.html │ └── vite.config.js ├── floating-player.html ├── fluid.html ├── force-hls-subtitles-ios.html ├── highlights-graph.html ├── index.html ├── interaction-area.html ├── live-streaming.html ├── multiple-players.html ├── playlist-by-tag-captions.html ├── playlist.html ├── poster.html ├── profiles.html ├── raw-url.html ├── recommendations.html ├── scripts.js ├── seek-thumbs.html ├── shoppable.html ├── subtitles-and-captions.html ├── transformations.html ├── ui-config.html ├── vast-vpaid.html └── visual-search.html ├── env.example.js ├── index.html ├── jest-puppeteer.config.js ├── jest.config.js ├── package-lock.json ├── package.json ├── src ├── assets │ ├── icon-font │ │ ├── README.md │ │ ├── VideoJS.svg │ │ ├── VideoJS.ttf │ │ ├── VideoJS.woff │ │ ├── custom-icons │ │ │ └── cld │ │ │ │ ├── check.svg │ │ │ │ ├── forward-10.svg │ │ │ │ ├── fullscreen-exit.svg │ │ │ │ ├── fullscreen.svg │ │ │ │ ├── pause.svg │ │ │ │ ├── picture-in-picture-off.svg │ │ │ │ ├── picture-in-picture-on.svg │ │ │ │ ├── play.svg │ │ │ │ ├── replay-10.svg │ │ │ │ ├── settings.svg │ │ │ │ ├── subtitles-off.svg │ │ │ │ ├── subtitles-on.svg │ │ │ │ ├── volume-down.svg │ │ │ │ ├── volume-off.svg │ │ │ │ └── volume-up.svg │ │ └── icons.json │ ├── icons │ │ ├── cloudinary_icon_for_black_bg.svg │ │ ├── cloudinary_icon_for_white_bg.svg │ │ └── info-circle.svg │ └── styles │ │ ├── _icons.scss │ │ ├── components │ │ ├── loading-button.scss │ │ ├── text-tracks.scss │ │ └── title-bar.scss │ │ ├── main.scss │ │ ├── mixins │ │ ├── aspect-ratio.scss │ │ ├── disable-transition.scss │ │ └── mixins.scss │ │ └── variables.scss ├── components │ ├── component-utils.js │ ├── index.js │ ├── jumpButtons │ │ ├── jump-10-minus.js │ │ └── jump-10-plus.js │ ├── logoButton │ │ ├── logo-button.js │ │ └── logo-button.scss │ ├── progress-control-events-blocker │ │ └── progress-control-events-blocker.js │ ├── recommendations-overlay │ │ ├── index.js │ │ ├── recommendations-overlay-content.js │ │ ├── recommendations-overlay-hide-button.js │ │ ├── recommendations-overlay-item.js │ │ ├── recommendations-overlay-primary-item.js │ │ ├── recommendations-overlay-secondary-item.js │ │ ├── recommendations-overlay-secondary-items-container.js │ │ ├── recommendations-overlay.js │ │ └── recommendations-overlay.scss │ ├── shoppable-bar │ │ ├── layout │ │ │ ├── bar-layout.js │ │ │ ├── shoppable-panel-toggle.js │ │ │ └── shoppable-products-overlay.js │ │ ├── panel │ │ │ ├── shoppable-panel-item.js │ │ │ └── shoppable-panel.js │ │ ├── shoppable-post-widget.js │ │ ├── shoppable-widget.const.js │ │ ├── shoppable-widget.js │ │ └── shoppable-widget.scss │ ├── themeButton │ │ ├── themedButton.const.js │ │ └── themedButton.js │ └── title-bar │ │ └── title-bar.js ├── config │ └── defaults.js ├── extended-events.js ├── index.all.js ├── index.es.js ├── index.js ├── index.player.js ├── index.videoPlayer.js ├── mixins │ └── eventable.js ├── player.js ├── plugins │ ├── adaptive-streaming │ │ ├── abr-strategies.js │ │ ├── adaptive-streaming.js │ │ ├── index.js │ │ ├── quality-levels.js │ │ └── videojs-contrib-hlsjs.js │ ├── ai-highlights-graph │ │ ├── ai-highlights-graph.scss │ │ └── index.js │ ├── analytics │ │ └── index.js │ ├── autoplay-on-scroll │ │ └── index.js │ ├── chapters │ │ ├── chapters.js │ │ ├── chapters.scss │ │ └── index.js │ ├── cloudinary-analytics │ │ └── index.js │ ├── cloudinary │ │ ├── common.js │ │ ├── event-handler-registry.js │ │ ├── index.js │ │ └── models │ │ │ ├── audio-source │ │ │ ├── audio-source.const.js │ │ │ └── audio-source.js │ │ │ ├── base-source.js │ │ │ ├── image-source.js │ │ │ └── video-source │ │ │ ├── video-source.const.js │ │ │ ├── video-source.js │ │ │ └── video-source.utils.js │ ├── colors │ │ ├── colors.js │ │ ├── colors.scss │ │ └── index.js │ ├── context-menu │ │ ├── components │ │ │ ├── context-menu-item.js │ │ │ └── context-menu.js │ │ ├── context-menu.scss │ │ ├── contextMenuContent.js │ │ ├── index.js │ │ └── videojs-contextmenu.js │ ├── floating-player │ │ ├── floating-player.scss │ │ └── index.js │ ├── ima │ │ ├── ima.js │ │ └── index.js │ ├── index.js │ ├── interaction-areas │ │ ├── index.js │ │ ├── interaction-areas.const.js │ │ ├── interaction-areas.scss │ │ ├── interaction-areas.service.js │ │ └── interaction-areas.utils.js │ ├── paced-transcript │ │ └── index.js │ ├── playlist │ │ ├── index.js │ │ ├── playlist.js │ │ ├── ui │ │ │ ├── components │ │ │ │ ├── playlist-button.js │ │ │ │ ├── playlist-buttons.js │ │ │ │ ├── playlist-buttons.scss │ │ │ │ ├── playlist-next-button.js │ │ │ │ ├── playlist-previous-button.js │ │ │ │ ├── upcoming-video-overlay.js │ │ │ │ └── upcoming-video-overlay.scss │ │ │ ├── layout │ │ │ │ ├── playlist-layout-custom.js │ │ │ │ ├── playlist-layout-horizontal.js │ │ │ │ ├── playlist-layout-vertical.js │ │ │ │ └── playlist-layout.js │ │ │ ├── panel │ │ │ │ ├── playlist-panel-item.js │ │ │ │ └── playlist-panel.js │ │ │ ├── playlist-widget.js │ │ │ ├── playlist.const.js │ │ │ ├── playlist.js │ │ │ ├── playlist.scss │ │ │ └── thumbnail │ │ │ │ ├── thumbnail.js │ │ │ │ └── thumbnail.scss │ │ └── utils │ │ │ ├── api.js │ │ │ ├── dom.js │ │ │ └── time.js │ ├── shoppable-plugin │ │ └── index.js │ ├── srt-text-tracks │ │ ├── index.js │ │ └── srt-text-tracks.js │ ├── styled-text-tracks │ │ ├── index.js │ │ ├── styled-text-tracks.js │ │ └── styled-text-tracks.scss │ ├── visual-search │ │ ├── components │ │ │ ├── SearchButton.js │ │ │ ├── SearchInput.js │ │ │ └── SearchResults.js │ │ ├── index.js │ │ ├── visual-search.js │ │ └── visual-search.scss │ └── vtt-thumbnails │ │ ├── index.js │ │ └── vtt-thumbnails.scss ├── utils │ ├── apply-with-props.js │ ├── attributes-normalizer.js │ ├── cloudinary.js │ ├── consts.js │ ├── css-prefix.js │ ├── dom.js │ ├── find.js │ ├── fontFace.js │ ├── get-analytics-player-options.js │ ├── index.js │ ├── mixin.js │ ├── object.js │ ├── positioning.js │ ├── querystring.js │ ├── slicing.js │ ├── time.js │ └── video-retry.js ├── validators │ ├── validators-functions.js │ ├── validators-types.js │ └── validators.js ├── video-player.const.js ├── video-player.js └── video-player.utils.js ├── test ├── adaptive-streaming.test.js ├── ads.test.js ├── analytics.test.js ├── api.test.js ├── autoplay.scroll.test.js ├── basic-ui.test.js ├── colors.test.js ├── components.test.js ├── custom-error.test.js ├── e2e │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc │ ├── components │ │ ├── BaseComponent.ts │ │ └── videoComponent.ts │ ├── fixtures │ │ └── vpTest.ts │ ├── playwright.config.ts │ ├── specs │ │ ├── ESM │ │ │ ├── esmAnalyticsPage.spec.ts │ │ │ ├── esmApiAndEventsPage.spec.ts │ │ │ ├── esmAudioPlayerPage.spec.ts │ │ │ ├── esmAutoplayOnScroll.spec.ts │ │ │ ├── esmChaptersPage.spec.ts │ │ │ ├── esmCldAnalyticsPage.spec.ts │ │ │ ├── esmCodecsAndFormatsPage.spec.ts │ │ │ ├── esmColorsApiPage.spec.ts │ │ │ ├── esmComponentsPage.spec.ts │ │ │ ├── esmDisplayConfigurationPage.spec.ts │ │ │ ├── esmFloatingPlayer.spec.ts │ │ │ ├── esmFluidLayoutsPage.spec.ts │ │ │ ├── esmForceHlsSubtitlesPage.spec.ts │ │ │ ├── esmHighlightsGraphPage.spec.ts │ │ │ ├── esmMainPageVideoIsPlaying.spec.ts │ │ │ ├── esmMultiplePlayersPage.spec.ts │ │ │ ├── esmPlaylistByTag.spec.ts │ │ │ ├── esmPlaylistPage.spec.ts │ │ │ ├── esmPosterOptionsPage.spec.ts │ │ │ ├── esmProfilesPage.spec.ts │ │ │ ├── esmRawUrlPage.spec.ts │ │ │ ├── esmRecommendationsPage.spec.ts │ │ │ ├── esmSeekThumbnailsPage.spec.ts │ │ │ ├── esmShoppableVideosPage.spec.ts │ │ │ ├── esmSubtitlesAndCaptionsPage.spec.ts │ │ │ ├── esmVastAndVpaidPage.spec.ts │ │ │ ├── esmVideoTransformationsPage.spec.ts │ │ │ ├── esmVr360Page.spec.ts │ │ │ └── linksConsoleErrorsEsmPage.spec.ts │ │ ├── NonESM │ │ │ ├── analyticsPage.spec.ts │ │ │ ├── apiAndEventsPage.spec.ts │ │ │ ├── audioPlayerPage.spec.ts │ │ │ ├── autoplayOnScrollPage.spec.ts │ │ │ ├── chaptersPage.spec.ts │ │ │ ├── cldAnalyticsPage.spec.ts │ │ │ ├── codecsAndFormats.spec.ts │ │ │ ├── colorsApiPage.spec.ts │ │ │ ├── componentsPage.spec.ts │ │ │ ├── displayConfigurationsPage.spec.ts │ │ │ ├── floatingPlayerPgae.spec.ts │ │ │ ├── fluidLayoutsPage.spec.ts │ │ │ ├── forceHlsSubtitlesPage.spec.ts │ │ │ ├── highlightsGraphPageVideoIsPlaying.spec.ts │ │ │ ├── linksConsolErros.spec.ts │ │ │ ├── mainPageVideoIsPlaying.spec.ts │ │ │ ├── multiplePlayersPage.spec.ts │ │ │ ├── playlistByTagPage.spec.ts │ │ │ ├── playlistPage.spec.ts │ │ │ ├── posterOptionsPage.spec.ts │ │ │ ├── profilesPage.spec.ts │ │ │ ├── rawUrlPage.spec.ts │ │ │ ├── recommendationsPage.spec.ts │ │ │ ├── seekThumbnailsPage.spec.ts │ │ │ ├── shoppableVideosPage.spec.ts │ │ │ ├── subtitlesAndCaptionsPage.spec.ts │ │ │ ├── vastAndVpaidPage.spec.ts │ │ │ ├── videoTransformationsPage.spec.ts │ │ │ ├── visualSearchPage.spec.ts │ │ │ └── vr360VideosPage.spec.ts │ │ ├── Setup │ │ │ └── global.setup.ts │ │ └── commonSpecs │ │ │ ├── apiAndEventsPageVideoPlaying.ts │ │ │ ├── audioPlayerPageVideoPlaying.ts │ │ │ ├── autoplayOnScrollVideoPlaying.ts │ │ │ ├── chaptersPage.ts │ │ │ ├── cldAnalyticsPageVideoPlaying.ts │ │ │ ├── codecsAndFormatsVideoPlaying.ts │ │ │ ├── colorsApiPageVideoPlaying.ts │ │ │ ├── componentsPageVideoPlaying.ts │ │ │ ├── displayConfigurationPageVideoPlaying.ts │ │ │ ├── floatingPlayerPageVideoPlaying.ts │ │ │ ├── fluidLayoutsPageVideoPlaying.ts │ │ │ ├── forceHlsSubtitlesPageVideoPlaying.ts │ │ │ ├── mainPageVideoPlaying.ts │ │ │ ├── multiplePlayersPageVideoPlaying.ts │ │ │ ├── playlistByTagPageVideoPlaying.ts │ │ │ ├── playlistPageVideoPlaying.ts │ │ │ ├── posterOptionsPage.ts │ │ │ ├── profilesPageVideoPlaying.ts │ │ │ ├── rawUrlPageVideoPlaying.ts │ │ │ ├── recommendationsPageVideoPlaying.ts │ │ │ ├── seekThumbnailsPageVideoPlaying.ts │ │ │ ├── shoppableVideosPageVideoPlaying.ts │ │ │ ├── subtitlesAndCaptionsPgaeVideoPlaying.ts │ │ │ ├── vastAndVpaidPage.ts │ │ │ ├── videoTransformationsPage.ts │ │ │ └── vr360VideosPageVideoPlaying.ts │ ├── src │ │ ├── helpers │ │ │ ├── validatePageErrors.ts │ │ │ └── waitForPageToLoadWithTimeout.ts │ │ └── pom │ │ │ ├── BasePage.ts │ │ │ ├── PageManager.ts │ │ │ ├── analyticsPage.ts │ │ │ ├── apiAndEventsPage.ts │ │ │ ├── audioPlayerPage.ts │ │ │ ├── autoplayOnScrollPage.ts │ │ │ ├── chaptersPage.ts │ │ │ ├── cldAnalyticsPage.ts │ │ │ ├── codecsAndFormats.ts │ │ │ ├── colorsApiPage.ts │ │ │ ├── componentsPage.ts │ │ │ ├── displayConfigurationsPage.ts │ │ │ ├── floatingPlayerPgae.ts │ │ │ ├── fluidLayoutsPage.ts │ │ │ ├── forceHlsSubtitlesPage.ts │ │ │ ├── highlightsGraphPage.ts │ │ │ ├── mainPage.ts │ │ │ ├── multiplePlayersPage.ts │ │ │ ├── playlistByTagPage.ts │ │ │ ├── playlistPage.ts │ │ │ ├── posterOptionsPage.ts │ │ │ ├── profilesPage.ts │ │ │ ├── rawUrlPage.ts │ │ │ ├── recommendationsPage.ts │ │ │ ├── seekThumbnailsPage.ts │ │ │ ├── shoppableVideosPage.ts │ │ │ ├── subtitlesAndCaptionsPage.ts │ │ │ ├── vastAndVpaidPage.ts │ │ │ ├── videoTransformationsPage.ts │ │ │ ├── visualSearchPage.ts │ │ │ └── vr360VideosPage.ts │ ├── testData │ │ ├── ExampleLinkNames.ts │ │ ├── esmPageLinksData.ts │ │ ├── esmUrl.ts │ │ └── pageLinksData.ts │ └── types │ │ └── exampleLinkType.ts ├── fluid.test.js ├── isValidConfig.test.js ├── mocks │ ├── cloudinary-core-mock.js │ └── styleMock.js ├── multiplayer.test.js ├── playlist.test.js ├── puppeteer │ └── vp-env.js ├── recommendations.test.js ├── title-bar.test.js ├── ui-conf.test.js └── unit │ ├── cloudinaryConfig.test.js │ ├── cloudinaryUtils.test.js │ └── videoSource.test.js ├── tsconfig.json ├── types ├── cld-video-player-tests.ts └── cld-video-player.d.ts └── webpack ├── analyzer.config.js ├── build-utils.js ├── build.config.js ├── common.config.js ├── copy-light-bundle.js ├── dev.config.js ├── es6.config.js └── puppeteer.config.js /.eslintignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | 3 | /types/ 4 | /test/types/api.test.ts 5 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | node: true, 5 | commonjs: true, 6 | es6: true, 7 | jest: true 8 | }, 9 | extends: ['eslint:recommended'], 10 | parserOptions: { 11 | ecmaVersion: 'latest', 12 | sourceType: 'module' 13 | }, 14 | 15 | globals: { 16 | VERSION: true, 17 | page: true, 18 | browser: true 19 | }, 20 | rules: { 21 | indent: ['error', 2, { SwitchCase: 1 }], 22 | 'linebreak-style': ['error', 'unix'], 23 | quotes: ['error', 'single'], 24 | semi: ['warn', 'always'], 25 | 'no-console': 'off', 26 | 'no-unused-vars': 'warn', 27 | 'no-useless-escape': 'off' 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @cloudinary/video 2 | -------------------------------------------------------------------------------- /.github/workflows/deprecate.yml: -------------------------------------------------------------------------------- 1 | name: ⛔ Deprecate 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | tag: 7 | description: 'Tag to update' 8 | required: true 9 | type: choice 10 | options: 11 | - edge 12 | - latest 13 | version: 14 | description: 'Version to deprecate' 15 | required: true 16 | tag_version: 17 | description: 'Version to use instead' 18 | required: true 19 | 20 | jobs: 21 | deprecate: 22 | runs-on: ubuntu-latest 23 | 24 | steps: 25 | - name: Setup Node.js 26 | uses: actions/setup-node@v4 27 | with: 28 | node-version: '20.x' 29 | registry-url: 'https://registry.npmjs.org' 30 | 31 | - name: Deprecate version 32 | run: npm deprecate cloudinary-video-player@${{ github.event.inputs.version }} "This version of cloudinary-video-player has been deprecated." 33 | env: 34 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 35 | 36 | - name: Set tag version 37 | run: npm dist-tag add cloudinary-video-player@${{ github.event.inputs.tag_version }} ${{ github.event.inputs.tag }} 38 | env: 39 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 40 | -------------------------------------------------------------------------------- /.github/workflows/validate.yml: -------------------------------------------------------------------------------- 1 | name: 🚧 Validate 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | validate: 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - name: Checkout code 12 | uses: actions/checkout@v4 13 | 14 | - name: Setup Node.js 15 | uses: actions/setup-node@v3 16 | with: 17 | node-version: '20.x' 18 | registry-url: 'https://registry.npmjs.org' 19 | 20 | - name: Install dependencies 21 | run: npm ci 22 | 23 | - name: Build 24 | run: npm run build-all 25 | 26 | - name: Unit tests 27 | run: npm run test:unit -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IDE 2 | .idea/ 3 | .tern-port 4 | tags 5 | 6 | # nodejs 7 | node_modules/ 8 | npm-debug.log 9 | yarn-error.log 10 | 11 | # project 12 | env.js 13 | dist/ 14 | lib/ 15 | .DS_Store 16 | 17 | \.history/ 18 | 19 | # playwright e2e tests 20 | 21 | /test-results/ 22 | /playwright-report/ 23 | /blob-report/ 24 | /playwright/.cache/ -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npm run commitlint ${1} 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | save_exact=true 2 | registry=https://registry.npmjs.org 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "singleQuote": true, 4 | "tabWidth": 2, 5 | "useTabs": false, 6 | "trailingComma": "none", 7 | "arrowParens": "avoid" 8 | } 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Cloudinary 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-env', 5 | { 6 | targets: { 7 | browsers: ['>0.25%', 'not ie 11', 'not op_mini all'] 8 | } 9 | } 10 | ] 11 | ], 12 | env: { 13 | test: { 14 | plugins: ['@babel/plugin-transform-runtime'] 15 | } 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { extends: ['@commitlint/config-conventional'] }; 2 | -------------------------------------------------------------------------------- /docs/es-modules/netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | ignore = "false" 3 | -------------------------------------------------------------------------------- /docs/es-modules/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cld-vp-es-examples", 3 | "private": true, 4 | "version": "0.0.1", 5 | "scripts": { 6 | "prepare-player": "cd ../../ && npm i && npm run build-all", 7 | "install-player": "npm i ../../ --no-save", 8 | "prepare": "npm run prepare-player && npm run install-player", 9 | "update-edge": "npm i", 10 | "start": "vite", 11 | "build": "vite build", 12 | "preview": "vite preview" 13 | }, 14 | "devDependencies": { 15 | "vite": "^6.0.2" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /docs/es-modules/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import { resolve } from 'path'; 3 | import fs from 'fs'; 4 | 5 | 6 | export default defineConfig({ 7 | build: { 8 | rollupOptions: { 9 | input: fs.readdirSync(resolve(__dirname)).filter(file => file.endsWith('.html')) 10 | } 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /env.example.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | devServer: { 3 | host: 'localhost', 4 | port: 3000 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /jest-puppeteer.config.js: -------------------------------------------------------------------------------- 1 | // workaround for find-process sudo 2 | global.process.getuid = () => 0; 3 | module.exports = { 4 | server: { 5 | command: 'node_modules/webpack-dev-server/bin/webpack-dev-server.js --config webpack/puppeteer.config.js', 6 | port: 3000, 7 | launchTimeout: 200000 8 | }, 9 | launch: { 10 | headless: true 11 | }, 12 | browserContext: 'default' 13 | 14 | }; 15 | -------------------------------------------------------------------------------- /src/assets/icon-font/README.md: -------------------------------------------------------------------------------- 1 | # Cloudinary Video Player Icons 2 | 3 | ## How to generate an updated icon-font 4 | 5 | Use the utility from https://github.com/videojs/font with the custom icons in the `cld` folder and the configuration in the `icons.json` provided here. 6 | 7 | MUI3 icons can be found [here](https://github.com/google/material-design-icons/blob/3.0.2/sprites/css-sprite/sprite-action-black.png). 8 | 9 | Copy the generated `videojs-icons.scss` file to the `styles` folder. 10 | -------------------------------------------------------------------------------- /src/assets/icon-font/VideoJS.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudinary/cloudinary-video-player/847f64997b6c8527858e16c45f718fcf9da2ff46/src/assets/icon-font/VideoJS.ttf -------------------------------------------------------------------------------- /src/assets/icon-font/VideoJS.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudinary/cloudinary-video-player/847f64997b6c8527858e16c45f718fcf9da2ff46/src/assets/icon-font/VideoJS.woff -------------------------------------------------------------------------------- /src/assets/icon-font/custom-icons/cld/check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/icon-font/custom-icons/cld/forward-10.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icon-font/custom-icons/cld/fullscreen-exit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icon-font/custom-icons/cld/fullscreen.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icon-font/custom-icons/cld/pause.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icon-font/custom-icons/cld/picture-in-picture-off.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icon-font/custom-icons/cld/picture-in-picture-on.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icon-font/custom-icons/cld/play.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icon-font/custom-icons/cld/replay-10.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icon-font/custom-icons/cld/settings.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icon-font/custom-icons/cld/subtitles-off.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icon-font/custom-icons/cld/subtitles-on.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icon-font/custom-icons/cld/volume-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icon-font/custom-icons/cld/volume-off.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icon-font/custom-icons/cld/volume-up.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/cloudinary_icon_for_black_bg.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/cloudinary_icon_for_white_bg.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/assets/icons/info-circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | FAB45380-2D6C-4F74-A415-E842A0488451 5 | Created with sketchtool. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/assets/styles/components/loading-button.scss: -------------------------------------------------------------------------------- 1 | $border-radius : 4px; 2 | 3 | .cld-video-player { 4 | 5 | .vp-theme-button { 6 | position: relative; 7 | height: 40px; 8 | font-size: 16px; 9 | border-radius: $border-radius; 10 | padding: 0 20px; 11 | overflow: hidden; 12 | 13 | &.theme-transparent-white { 14 | background-color: rgba(255, 255, 254, 0.18); 15 | 16 | &:hover { 17 | background-color: rgba(255, 255, 254, 0.28); 18 | } 19 | 20 | .vp-loading-bar { 21 | background-color: rgba(255, 255, 254, 0.22); 22 | } 23 | 24 | } 25 | 26 | .vp-loading-bar { 27 | position: absolute; 28 | top:0; 29 | left: 0; 30 | height: 100%; 31 | width: 100%; 32 | transition: width; 33 | animation-name: loading; 34 | } 35 | 36 | &:hover{ 37 | cursor: pointer; 38 | } 39 | 40 | } 41 | 42 | } 43 | 44 | 45 | @keyframes loading { 46 | from { width: 0; } 47 | to { width: 100%; } 48 | } 49 | -------------------------------------------------------------------------------- /src/assets/styles/components/text-tracks.scss: -------------------------------------------------------------------------------- 1 | .cld-video-player { 2 | // Default captions styles 3 | // See styled-text-tracks plugin for more options 4 | .vjs-text-track-display { 5 | // When control-bar is shown 6 | bottom: 5em; 7 | z-index: 2; 8 | // Word highlight 9 | &.cld-paced-text-tracks b { 10 | color: var(--color-accent); 11 | } 12 | } 13 | &.vjs-controls-disabled .vjs-text-track-display, 14 | &.vjs-user-inactive.vjs-playing .vjs-text-track-display { 15 | // When control-bar is hidden/disabled 16 | bottom: 1em; 17 | } 18 | 19 | .vjs-text-track-cue { 20 | max-width: 100%; 21 | > div { 22 | display: inline-block !important; 23 | padding: 0.1em 0.3em; 24 | background-color: rgba(0, 0, 0, 0.5) !important; 25 | } 26 | } 27 | 28 | // Word highlight - default style 29 | .cld-paced-text-tracks .vjs-text-track-cue b { 30 | color: var(--color-accent); 31 | } 32 | 33 | // Default caption styles, when not configured to `videojs-default` theme 34 | .vjs-text-track-display:not(.cld-styled-text-tracks-theme-videojs-default) { 35 | .vjs-text-track-cue { 36 | font-family: inherit !important; 37 | > div { 38 | font-weight: 700; 39 | background-color: transparent !important; 40 | text-shadow: 0 0 0.2em rgba(0, 0, 0, 0.8); 41 | border-radius: 0.2em; 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/assets/styles/mixins/aspect-ratio.scss: -------------------------------------------------------------------------------- 1 | @use "sass:math"; 2 | 3 | @mixin aspect-ratio($width, $height) { 4 | &:before { 5 | display: block; 6 | content: ""; 7 | width: 100%; 8 | padding-top: math.div($height, $width) * 100%; 9 | } 10 | 11 | > .aspect-ratio-content { 12 | position: absolute; 13 | top: 0; 14 | left: 0; 15 | right: 0; 16 | bottom: 0; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/assets/styles/mixins/disable-transition.scss: -------------------------------------------------------------------------------- 1 | &.disable-transition { 2 | transition: visibility 0s; 3 | } 4 | -------------------------------------------------------------------------------- /src/assets/styles/mixins/mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin ellipsis { 2 | white-space: nowrap; 3 | overflow: hidden; 4 | text-overflow: ellipsis; 5 | } -------------------------------------------------------------------------------- /src/assets/styles/variables.scss: -------------------------------------------------------------------------------- 1 | $text-font-family: Arial, Helvetica, sans-serif; 2 | $center-big-play-button: true; 3 | -------------------------------------------------------------------------------- /src/components/component-utils.js: -------------------------------------------------------------------------------- 1 | const hide = (el) => { 2 | el.style.display = 'none'; 3 | }; 4 | 5 | const show = (el) => { 6 | el.style.display = ''; 7 | }; 8 | 9 | const setText = (el, text) => { 10 | if (!text || text.length <= 0) { 11 | el.innerText = ''; 12 | hide(el); 13 | return; 14 | } 15 | 16 | el.innerText = text; 17 | show(el); 18 | }; 19 | 20 | module.exports = { hide, show, setText }; 21 | -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | import JumpForwardButton from './jumpButtons/jump-10-plus'; 2 | import JumpBackButton from './jumpButtons/jump-10-minus'; 3 | import LogoButton from './logoButton/logo-button'; 4 | import ProgressControlEventsBlocker from './progress-control-events-blocker/progress-control-events-blocker'; 5 | import TitleBar from './title-bar/title-bar'; 6 | 7 | export { 8 | JumpForwardButton, 9 | JumpBackButton, 10 | LogoButton, 11 | ProgressControlEventsBlocker, 12 | TitleBar 13 | }; 14 | -------------------------------------------------------------------------------- /src/components/jumpButtons/jump-10-minus.js: -------------------------------------------------------------------------------- 1 | import videojs from 'video.js'; 2 | 3 | const ClickableComponent = videojs.getComponent('ClickableComponent'); 4 | 5 | class JumpBackButton extends ClickableComponent { 6 | 7 | handleClick(event) { 8 | super.handleClick(event); 9 | this.player().currentTime(this.player().currentTime() - 10); 10 | } 11 | 12 | createEl() { 13 | return videojs.dom.createEl('button', { 14 | className: 'vjs-control vjs-icon-skip-10-min vjs-icon-replay-10 vjs-button', 15 | ariaLabel: 'Jump back 10 seconds' 16 | }); 17 | } 18 | } 19 | 20 | videojs.registerComponent('JumpBackButton', JumpBackButton); 21 | 22 | export default JumpBackButton; 23 | -------------------------------------------------------------------------------- /src/components/jumpButtons/jump-10-plus.js: -------------------------------------------------------------------------------- 1 | import videojs from 'video.js'; 2 | 3 | const ClickableComponent = videojs.getComponent('ClickableComponent'); 4 | 5 | class JumpForwardButton extends ClickableComponent { 6 | handleClick(event) { 7 | super.handleClick(event); 8 | this.player().currentTime(this.player().currentTime() + 10); 9 | } 10 | 11 | createEl() { 12 | return videojs.dom.createEl('button', { 13 | className: 'vjs-control vjs-icon-skip-10-plus vjs-icon-forward-10 vjs-button', 14 | ariaLabel: 'Jump forward 10 seconds' 15 | }); 16 | } 17 | } 18 | 19 | videojs.registerComponent('JumpForwardButton', JumpForwardButton); 20 | 21 | export default JumpForwardButton; 22 | -------------------------------------------------------------------------------- /src/components/logoButton/logo-button.js: -------------------------------------------------------------------------------- 1 | import videojs from 'video.js'; 2 | import './logo-button.scss'; 3 | 4 | // support VJS5 & VJS6 at the same time 5 | const ClickableComponent = videojs.getComponent('ClickableComponent'); 6 | 7 | class LogoButton extends ClickableComponent { 8 | 9 | createEl() { 10 | const opts = this.options_.playerOptions; 11 | const display = opts.showLogo ? 'block' : 'none'; 12 | 13 | const bgImage = opts.logoImageUrl ? `background-image: url(${opts.logoImageUrl})` : ''; 14 | 15 | return videojs.dom.createEl('a', {}, { 16 | class: 'vjs-control vjs-cloudinary-button vjs-button', 17 | href: opts.logoOnclickUrl, 18 | target: '_blank', 19 | style: `display: ${display}; ${bgImage}`, 20 | 'aria-label': 'Logo link' 21 | }); 22 | } 23 | } 24 | 25 | videojs.registerComponent('logoButton', LogoButton); 26 | 27 | export default LogoButton; 28 | -------------------------------------------------------------------------------- /src/components/logoButton/logo-button.scss: -------------------------------------------------------------------------------- 1 | // Player Cloudinary button 2 | .vjs-control-bar a.vjs-control.vjs-cloudinary-button { 3 | background-image: url("../../assets/icons/cloudinary_icon_for_black_bg.svg"); 4 | background-size: 25px; 5 | background-position: center; 6 | background-repeat: no-repeat; 7 | color: inherit; 8 | 9 | .cld-video-player-skin-light & { 10 | background-image: url("../../assets/icons/cloudinary_icon_for_white_bg.svg"); 11 | } 12 | 13 | &:hover { 14 | cursor: pointer; 15 | } 16 | 17 | &:last-child { 18 | margin-right: 0.4em; 19 | margin-left: 0.8em; 20 | &::before { 21 | content: ''; 22 | position: absolute; 23 | left: -0.25em; 24 | top: 0.3em; 25 | bottom: 0.3em; 26 | border-left: 1px solid currentColor; 27 | opacity: 0.25; 28 | } 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/components/progress-control-events-blocker/progress-control-events-blocker.js: -------------------------------------------------------------------------------- 1 | import videojs from 'video.js'; 2 | const Component = videojs.getComponent('Component'); 3 | 4 | class ProgressControlEventsBlocker extends Component { 5 | 6 | constructor(player, options = {}) { 7 | super(player, options); 8 | } 9 | 10 | createEl() { 11 | return super.createEl('div', { 12 | className: 'vjs-progress-control-events-blocker' 13 | }); 14 | } 15 | } 16 | 17 | videojs.registerComponent('progressControlEventsBlocker', ProgressControlEventsBlocker); 18 | 19 | export default ProgressControlEventsBlocker; 20 | -------------------------------------------------------------------------------- /src/components/recommendations-overlay/index.js: -------------------------------------------------------------------------------- 1 | export default async function lazyRecommendationsOverlayComponent(player) { 2 | try { 3 | if (!player.getChild('recommendationsOverlay')) { 4 | await import(/* webpackChunkName: "recommendations-overlay" */ './recommendations-overlay'); 5 | player.addChild('recommendationsOverlay'); 6 | } 7 | return player; 8 | } catch (error) { 9 | console.error('Failed to load plugin:', error); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/components/recommendations-overlay/recommendations-overlay-hide-button.js: -------------------------------------------------------------------------------- 1 | import videojs from 'video.js'; 2 | 3 | const ClickableComponent = videojs.getComponent('ClickableComponent'); 4 | 5 | class RecommendationOverlayHideButton extends ClickableComponent { 6 | 7 | createEl() { 8 | return super.createEl('span', { 9 | className: 'vjs-recommendations-overlay-hide vjs-icon-close' 10 | }); 11 | } 12 | 13 | handleClick() { 14 | this.options_.clickHandler(); 15 | } 16 | } 17 | 18 | export default RecommendationOverlayHideButton; 19 | -------------------------------------------------------------------------------- /src/components/recommendations-overlay/recommendations-overlay-item.js: -------------------------------------------------------------------------------- 1 | import videojs from 'video.js'; 2 | 3 | const ClickableComponent = videojs.getComponent('ClickableComponent'); 4 | 5 | class RecommendationsOverlayItem extends ClickableComponent { 6 | 7 | setItem(item) { 8 | const { action, source } = item; 9 | this.source = source; 10 | 11 | const info = source.info(); 12 | 13 | this.setTitle(info.title || source.publicId()); 14 | 15 | this.setPoster(this.source.poster().url({ transformation: { aspect_ratio: '16:9', crop: 'pad', background: 'black' } })); 16 | 17 | this.setAction(action); 18 | } 19 | 20 | setTitle(text) { 21 | this.title.innerText = text; 22 | } 23 | 24 | setAction(action) { 25 | this.action = action; 26 | } 27 | 28 | handleClick() { 29 | super.handleClick(); 30 | this.player().trigger('recommendationshide'); 31 | this.action(); 32 | } 33 | } 34 | 35 | export default RecommendationsOverlayItem; 36 | -------------------------------------------------------------------------------- /src/components/recommendations-overlay/recommendations-overlay-secondary-item.js: -------------------------------------------------------------------------------- 1 | import videojs from 'video.js'; 2 | import RecommendationsOverlayItem from './recommendations-overlay-item'; 3 | 4 | // support VJS5 & VJS6 at the same time 5 | const dom = videojs.dom || videojs; 6 | 7 | class RecommendationsOverlaySecondaryItem extends RecommendationsOverlayItem { 8 | 9 | setItem(item) { 10 | super.setItem(item); 11 | this.setDuration(''); 12 | } 13 | 14 | setPoster(url) { 15 | this.el().style.backgroundImage = `url('${url}')`; 16 | } 17 | 18 | setDuration(text) { 19 | this.duration.innerText = text; 20 | } 21 | 22 | createEl() { 23 | const el = super.createEl('div', { 24 | className: 'vjs-recommendations-overlay-item vjs-recommendations-overlay-item-secondary' 25 | }); 26 | 27 | this.title = dom.createEl('span', { className: 'vjs-recommendations-overlay-item-secondary-title' }); 28 | this.title.innerHTML = ''; 29 | 30 | this.duration = dom.createEl('span', { className: 'vjs-recommendations-overlay-item-secondary-duration' }); 31 | this.duration.innerHTML = ''; 32 | 33 | const caption = dom.createEl('div', { className: 'vjs-recommendations-overlay-item-info' }); 34 | caption.appendChild(this.title); 35 | caption.appendChild(this.duration); 36 | 37 | el.appendChild(caption); 38 | 39 | return el; 40 | } 41 | 42 | handleClick() { 43 | super.handleClick(); 44 | this.action(); 45 | } 46 | } 47 | 48 | export default RecommendationsOverlaySecondaryItem; 49 | -------------------------------------------------------------------------------- /src/components/recommendations-overlay/recommendations-overlay-secondary-items-container.js: -------------------------------------------------------------------------------- 1 | import videojs from 'video.js'; 2 | import RecommendationsOverlaySecondaryItem from './recommendations-overlay-secondary-item'; 3 | 4 | const Component = videojs.getComponent('Component'); 5 | 6 | class RecommendationsOverlaySecondaryItemsContainer extends Component { 7 | 8 | setItems(...items) { 9 | this.clearItems(); 10 | 11 | if (!items) { 12 | return; 13 | } 14 | 15 | items.forEach((item) => { 16 | const component = new RecommendationsOverlaySecondaryItem(this.player()); 17 | component.setItem(item); 18 | this.addChild(component); 19 | }); 20 | } 21 | 22 | clearItems() { 23 | this.children().forEach(() => { 24 | this.removeChild(this.children()[0]); 25 | }); 26 | } 27 | 28 | createEl() { 29 | return super.createEl('div', { 30 | className: 'vjs-recommendations-overlay-item-secondary-container' 31 | }); 32 | } 33 | } 34 | 35 | export default RecommendationsOverlaySecondaryItemsContainer; 36 | -------------------------------------------------------------------------------- /src/components/shoppable-bar/shoppable-widget.const.js: -------------------------------------------------------------------------------- 1 | export const SHOPPABLE_WIDGET_OPTIONS_DEFAULTS = { 2 | location: 'right', 3 | toggleIcon: '', 4 | width: '20%', 5 | startState: 'openOnPlay', 6 | autoClose: 2, 7 | transformation: { 8 | quality: 'auto', 9 | width: 'auto', 10 | fetch_format: 'auto', 11 | crop: 'scale' 12 | }, 13 | products: [], 14 | showPostPlayOverlay: false 15 | }; 16 | 17 | export const SHOPPABLE_CLICK_ACTIONS = { 18 | GO_TO: 'goto', 19 | SEEk: 'seek' 20 | }; 21 | 22 | export const SHOPPABLE_HOVER_ACTIONS = { 23 | OVERLAY: 'overlay' 24 | }; 25 | 26 | export const SHOPPABLE_PANEL_VISIBLE_CLASS = 'shoppable-panel-visible'; 27 | 28 | export const SHOPPABLE_PANEL_HIDDEN_CLASS = 'shoppable-panel-hidden'; 29 | 30 | export const SHOPPABLE_PRODUCTS_OVERLAY_CLASS = 'shoppable-products-overlay'; 31 | 32 | export const CLD_SPBL_PANEL_CLASS = 'cld-spbl-panel'; 33 | 34 | export const CLD_SPBL_TOGGLE_CLASS = 'cld-spbl-toggle'; 35 | 36 | export const CLD_SPBL_TOGGLE_ICON_CLASS = 'cld-spbl-toggle-icon'; 37 | 38 | export const CLD_SPBL_INNER_BAR = 'cld-spbl-bar-inner'; 39 | 40 | export const CLD_SPBL_TOGGLE_CUSTOM_ICON_CLASS = 'cld-spbl-toggle-custom-icon'; 41 | 42 | export const ICON_CART_CLASS = 'vjs-icon-cart'; 43 | 44 | export const CLOSE_ICON_CLASS = 'vjs-icon-close'; 45 | 46 | export const SHOPPABLE_ANIMATION_CLASS = 'animate'; 47 | 48 | export const CLD_SPBL_ITEM = 'cld-spbl-item'; 49 | 50 | export const CLD_SPBL_IMAGE = 'cld-spbl-img'; 51 | -------------------------------------------------------------------------------- /src/components/themeButton/themedButton.const.js: -------------------------------------------------------------------------------- 1 | export const BUTTON_THEME = { 2 | TRANSPARENT_WHITE: 'transparent-white' 3 | }; 4 | -------------------------------------------------------------------------------- /src/components/themeButton/themedButton.js: -------------------------------------------------------------------------------- 1 | import { elementsCreator } from '../../utils/dom'; 2 | 3 | 4 | export const themedButton = ({ text, onClick, theme = '', loadingDelay = 0 }) => { 5 | return elementsCreator({ 6 | tag: 'button', 7 | attr: { class: `vp-theme-button theme-${theme}` }, 8 | onClick, 9 | children: [ 10 | { 11 | tag: 'div', 12 | attr: { class: 'vp-loading-bar' }, 13 | style: { 14 | 'animation-duration': `${loadingDelay}ms` 15 | } 16 | }, 17 | { 18 | tag: 'div', 19 | attr: { class: 'content' }, 20 | children: text 21 | } 22 | ] 23 | }); 24 | }; 25 | 26 | -------------------------------------------------------------------------------- /src/config/defaults.js: -------------------------------------------------------------------------------- 1 | import videojs from 'video.js'; 2 | import contextMenuContent from '../plugins/context-menu/contextMenuContent'; 3 | import { FLOATING_TO, PRELOAD } from '../video-player.const'; 4 | 5 | export default { 6 | logoOnclickUrl: 'https://cloudinary.com/', 7 | showLogo: true, 8 | showJumpControls: false, 9 | playsinline: videojs.browser.IS_IOS, 10 | skin: 'dark', 11 | controls: false, 12 | chaptersButton: false, 13 | pictureInPictureToggle: false, 14 | seekThumbnails: true, 15 | aiHighlightsGraph: false, 16 | visualSearch: false, 17 | preload: PRELOAD.AUTO, 18 | textTrackSettings: false, 19 | loop: false, 20 | muted: false, 21 | posterOptions: {}, 22 | sourceTypes: ['auto'], 23 | contextMenu: { 24 | content: contextMenuContent 25 | }, 26 | floatingWhenNotVisible: FLOATING_TO.NONE, 27 | hideContextMenu: false, 28 | analytics: false, 29 | cloudinaryAnalytics: true, 30 | allowUsageReport: true, 31 | playedEventPercents: [25, 50, 75, 100], 32 | adaptiveStreaming: { 33 | strategy: 'balanced', 34 | }, 35 | html5: { 36 | handlePartialData: false, 37 | nativeTextTracks: false 38 | }, 39 | disableSeekWhileScrubbingOnMobile: true 40 | }; 41 | -------------------------------------------------------------------------------- /src/index.all.js: -------------------------------------------------------------------------------- 1 | // This file is bundled as `all.js` to be imported as a single module that includes all plugins. 2 | 3 | // Usage: 4 | // import { videoPlayer, videoPlayers } from 'cloudinary-video-player/all'; 5 | // Or: 6 | // import cloudinary from 'cloudinary-video-player/all'; 7 | 8 | import cloudinary from './index.js'; 9 | 10 | export * from './index.js'; 11 | export * from './plugins/adaptive-streaming/adaptive-streaming.js'; 12 | export * from './plugins/chapters/chapters.js'; 13 | export * from './plugins/colors/colors.js'; 14 | export * from './plugins/ima/ima.js'; 15 | export * from './plugins/playlist/playlist.js'; 16 | export * from './plugins/interaction-areas/interaction-areas.service.js'; 17 | export * from './plugins/visual-search/visual-search.js'; 18 | export * from './plugins/srt-text-tracks/srt-text-tracks.js'; 19 | export * from './components/shoppable-bar/shoppable-widget.js'; 20 | export * from './components/recommendations-overlay/recommendations-overlay.js'; 21 | 22 | export default cloudinary; 23 | -------------------------------------------------------------------------------- /src/index.es.js: -------------------------------------------------------------------------------- 1 | // This file is bundled as `cld-video-player.js` to be imported as a tree-shaken module. 2 | // It is the default export of the Cloudinary Video Player. 3 | 4 | // Usage: 5 | // import cloudinary from 'cloudinary-video-player'; 6 | // Or: 7 | // import { videoPlayer } from "cloudinary-video-player"; 8 | 9 | // Other modules can be imported like that: 10 | // import dash from 'cloudinary-video-player/dash'; 11 | 12 | import cloudinary from './index.js'; 13 | 14 | export const videoPlayer = cloudinary.videoPlayer; 15 | export const videoPlayers = cloudinary.videoPlayers; 16 | 17 | export const player = cloudinary.player; 18 | 19 | export default cloudinary; 20 | -------------------------------------------------------------------------------- /src/index.player.js: -------------------------------------------------------------------------------- 1 | // This file is bundled as `player.js` to be imported as a tree-shaken module. 2 | 3 | // Usage: 4 | // import player from 'cloudinary-video-player/player'; 5 | 6 | export { player as default } from './index.js'; 7 | -------------------------------------------------------------------------------- /src/index.videoPlayer.js: -------------------------------------------------------------------------------- 1 | // This file is bundled as `videoPlayer.js` to be imported as a tree-shaken module. 2 | 3 | // Usage: 4 | // import videoPlayer from 'cloudinary-video-player/videoPlayer'; 5 | 6 | export { videoPlayer as default } from './index.js'; 7 | -------------------------------------------------------------------------------- /src/mixins/eventable.js: -------------------------------------------------------------------------------- 1 | const Eventable = (superclass) => class extends superclass { 2 | constructor() { 3 | super(); 4 | 5 | const eventable = { data: {}, handlers: {} }; 6 | 7 | this.on = (...args) => { 8 | const lastIndex = args.length - 1; 9 | const func = args[lastIndex]; 10 | 11 | eventable.handlers[func] = (event, ..._args) => { 12 | event.Player = this; 13 | func(event, ..._args); 14 | }; 15 | 16 | args[lastIndex] = eventable.handlers[func]; 17 | 18 | return this.videojs.on(...args); 19 | }; 20 | 21 | this.one = (...args) => { 22 | const lastIndex = args.length - 1; 23 | const func = args[lastIndex]; 24 | 25 | eventable.handlers[func] = (event, ..._args) => { 26 | event.Player = this; 27 | func(event, ..._args); 28 | delete eventable.handlers[func]; 29 | }; 30 | 31 | args[lastIndex] = eventable.handlers[func]; 32 | 33 | return this.videojs.one(...args); 34 | }; 35 | 36 | this.off = (...args) => { 37 | const lastIndex = args.length - 1; 38 | const func = args[lastIndex]; 39 | 40 | args[lastIndex] = eventable.handlers[func]; 41 | 42 | const res = this.videojs.off(...args); 43 | delete eventable.handlers[func]; 44 | 45 | return res; 46 | }; 47 | 48 | this.trigger = (...args) => { 49 | this.videojs.trigger(...args); 50 | }; 51 | } 52 | }; 53 | 54 | export default Eventable; 55 | -------------------------------------------------------------------------------- /src/plugins/adaptive-streaming/abr-strategies.js: -------------------------------------------------------------------------------- 1 | export const abrStrategies = { 2 | fastStart: { 3 | capLevelToPlayerSize: true, 4 | ignoreDevicePixelRatio: true, 5 | maxDevicePixelRatio: 2, 6 | abrEwmaDefaultEstimate: 4194304, 7 | abrEwmaDefaultEstimateMax: 4194304, 8 | enableWorker: false, 9 | startLevel: 0 10 | }, 11 | balanced: { 12 | capLevelToPlayerSize: true, 13 | ignoreDevicePixelRatio: true, 14 | maxDevicePixelRatio: 2, 15 | abrEwmaDefaultEstimate: 4194304, 16 | abrEwmaDefaultEstimateMax: 4194304, 17 | enableWorker: false 18 | }, 19 | highQuality: { 20 | capLevelToPlayerSize: true, 21 | ignoreDevicePixelRatio: false, 22 | maxDevicePixelRatio: 2, 23 | abrEwmaDefaultEstimate: 4194304, 24 | abrEwmaDefaultEstimateMax: 4194304, 25 | enableWorker: false 26 | } 27 | }; 28 | 29 | export const ADAPTIVE_STREAMING_STRATEGY = Object.keys(abrStrategies); 30 | 31 | export const hdrSupported = window.matchMedia && window.matchMedia('(dynamic-range: high)').matches; 32 | -------------------------------------------------------------------------------- /src/plugins/adaptive-streaming/adaptive-streaming.js: -------------------------------------------------------------------------------- 1 | import 'hls.js'; 2 | import 'videojs-contrib-quality-levels'; 3 | import 'videojs-contrib-quality-menu'; 4 | import './videojs-contrib-hlsjs'; 5 | import { qualityLevels } from './quality-levels'; 6 | import { abrStrategies, hdrSupported } from './abr-strategies'; 7 | 8 | export default async function adaptiveStreamingPlugin(player, options) { 9 | const config = { 10 | ...abrStrategies[options.strategy], 11 | videoPreference: hdrSupported ? { preferHDR: true } : undefined 12 | }; 13 | player.tech_.options_.hlsjsConfig = config; 14 | player.on('loadstart', () => qualityLevels(player, options).init()); 15 | player.qualityMenu(); 16 | player.adaptiveStreamingLoaded = true; 17 | } 18 | -------------------------------------------------------------------------------- /src/plugins/adaptive-streaming/index.js: -------------------------------------------------------------------------------- 1 | export default async function lazyAdaptiveStreamingPlugin(options) { 2 | const player = this; 3 | 4 | try { 5 | if (options.isDash) { 6 | await import(/* webpackChunkName: "dash" */ 'videojs-contrib-dash'); 7 | } 8 | await import(/* webpackChunkName: "adaptive-streaming" */ './adaptive-streaming'); 9 | const { default: abrPlugin } = await import(/* webpackChunkName: "adaptive-streaming" */ './adaptive-streaming'); 10 | abrPlugin(player, options); 11 | } catch (error) { 12 | console.error('Failed to load plugin:', error); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/plugins/ai-highlights-graph/ai-highlights-graph.scss: -------------------------------------------------------------------------------- 1 | .cld-video-player { 2 | .vjs-highlights-graph-display { 3 | position: absolute; 4 | left: 0; 5 | right: 0; 6 | bottom: 50%; 7 | z-index: 0; 8 | pointer-events: none; 9 | opacity: 0; 10 | transition: opacity 0.2s; 11 | svg { 12 | width: 100%; 13 | height: 100%; 14 | } 15 | path { 16 | fill: currentColor; 17 | } 18 | } 19 | 20 | .vjs-progress-control:hover .vjs-highlights-graph-display { 21 | opacity: 0.8; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/plugins/chapters/chapters.scss: -------------------------------------------------------------------------------- 1 | .cld-video-player { 2 | .vjs-control-bar-chapter-wrapper { 3 | display: flex; 4 | align-items: center; 5 | container-type: inline-size; 6 | } 7 | .vjs-control-bar-chapter-display { 8 | line-height: 1.5; 9 | font-size: 90%; 10 | white-space: nowrap; 11 | overflow: hidden; 12 | text-overflow: ellipsis; 13 | padding: 0 0.5em; 14 | &:not(:empty)::before { 15 | content: '•'; 16 | padding-right: 0.5em; 17 | } 18 | @container (max-width: 150px) { 19 | display: none; 20 | } 21 | } 22 | 23 | .vjs-chapter-marker { 24 | pointer-events: none; 25 | position: absolute; 26 | background: var(--color-base); 27 | width: 4px; 28 | top: 0; 29 | bottom: 0; 30 | opacity: 0.5; 31 | z-index: 1; 32 | } 33 | 34 | .vjs-chapter-display { 35 | pointer-events: none; 36 | line-height: 1.5; 37 | font-size: 90%; 38 | white-space: nowrap; 39 | overflow: hidden; 40 | text-overflow: ellipsis; 41 | transform: translateX(-50%); 42 | bottom: 2.7em; 43 | position: absolute; 44 | padding: 0 0.3em; 45 | font-weight: bold; 46 | text-shadow: 0 0 8px var(--color-base), 0 0 1px var(--color-base), 0 0 1px var(--color-base); 47 | &:not(:empty) ~ .vjs-vtt-thumbnail-display { 48 | bottom: 4em; 49 | } 50 | } 51 | 52 | .vjs-time-tooltip { 53 | right: auto !important; 54 | translate: -50%; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/plugins/chapters/index.js: -------------------------------------------------------------------------------- 1 | export default async function lazyChaptersPlugin(options) { 2 | const player = this; 3 | try { 4 | const { default: initPlugin } = await import(/* webpackChunkName: "chapters" */ './chapters'); 5 | player.ready(() => initPlugin(options, player)); 6 | } catch (error) { 7 | console.error('Failed to load plugin:', error); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/plugins/cloudinary/event-handler-registry.js: -------------------------------------------------------------------------------- 1 | class EventHandlerRegistry { 2 | constructor(emitter) { 3 | this._emitter = emitter; 4 | this._eventHandlers = []; 5 | } 6 | 7 | on(type, handler) { 8 | this._eventHandlers.push({ type, handler }); 9 | this._emitter.on(type, handler); 10 | } 11 | 12 | one(type, handler) { 13 | const wrapper = (...args) => { 14 | handler(...args); 15 | this.off(type, handler); 16 | }; 17 | 18 | this._eventHandlers.push({ type, handler, wrapper }); 19 | this._emitter.one(type, handler); 20 | } 21 | 22 | off(type, handler) { 23 | const index = this._eventHandlers?.findIndex((event) => 24 | event.type === type && event.handler === handler); 25 | 26 | if (index === -1) { 27 | return; 28 | } 29 | 30 | const event = this._eventHandlers[index]; 31 | 32 | this._emitter.off(type, event.wrapper || event.handler); 33 | 34 | this._eventHandlers.splice(index, 1); 35 | } 36 | 37 | removeAllListeners() { 38 | this._eventHandlers.forEach((event) => { 39 | this.off(event); 40 | }); 41 | } 42 | } 43 | 44 | export default EventHandlerRegistry; 45 | -------------------------------------------------------------------------------- /src/plugins/cloudinary/models/audio-source/audio-source.const.js: -------------------------------------------------------------------------------- 1 | export const DEFAULT_POSTER_PARAMS = { format: 'jpg', resource_type: 'video', transformation: { flags: 'waveform' } }; 2 | 3 | export const COMMON_AUDIO_FORMATS = ['mp3', 'ogg', 'wav', 'mp4']; 4 | 5 | export const AUDIO_SUFFIX_REMOVAL_PATTERN = RegExp(`\\.(${COMMON_AUDIO_FORMATS.join('|')})$$`); 6 | 7 | export const DEFAULT_AUDIO_PARAMS = { 8 | resource_type: 'video', 9 | type: 'upload', 10 | transformation: [] 11 | }; 12 | -------------------------------------------------------------------------------- /src/plugins/cloudinary/models/image-source.js: -------------------------------------------------------------------------------- 1 | import BaseSource from './base-source'; 2 | import { normalizeOptions } from '../common'; 3 | 4 | const COMMON_IMAGE_FORMATS = ['jpg', 'png', 'gif', 'webp']; 5 | const IMAGE_SUFFIX_REMOVAL_PATTERN = RegExp(`\\.(${COMMON_IMAGE_FORMATS.join('|')})$$`); 6 | const DEFAULT_IMAGE_PARAMS = { 7 | resource_type: 'image', 8 | type: 'upload', 9 | transformation: [] 10 | }; 11 | 12 | class ImageSource extends BaseSource { 13 | constructor(publicId, options = {}) { 14 | ({ publicId, options } = normalizeOptions(publicId, options)); 15 | 16 | publicId = publicId.replace(IMAGE_SUFFIX_REMOVAL_PATTERN, ''); 17 | 18 | options = Object.assign({}, DEFAULT_IMAGE_PARAMS, options); 19 | 20 | super(publicId, options); 21 | this._type = 'ImageSource'; 22 | } 23 | } 24 | 25 | export default ImageSource; 26 | -------------------------------------------------------------------------------- /src/plugins/cloudinary/models/video-source/video-source.utils.js: -------------------------------------------------------------------------------- 1 | import { CONTAINER_MIME_TYPES, FORMAT_MAPPINGS } from './video-source.const'; 2 | import { VIDEO_CODEC } from '../../common'; 3 | import isString from 'lodash/isString'; 4 | import { isKeyInTransformation } from 'utils/cloudinary'; 5 | 6 | export function formatToMimeTypeAndTransformation(format) { 7 | const [container, codec] = format.toLowerCase().split('/'); 8 | const mimetype = CONTAINER_MIME_TYPES[container] || `video/${container}`; 9 | let result = [mimetype]; 10 | 11 | if (codec) { 12 | const transformation = { video_codec: codec }; 13 | result = [mimetype, transformation]; 14 | } 15 | 16 | return result; 17 | } 18 | 19 | export function normalizeFormat(format) { 20 | format = format.toLowerCase().split('/').shift(); 21 | 22 | let res = FORMAT_MAPPINGS[format]; 23 | if (!res) { 24 | res = format.split('/').shift(); 25 | } 26 | 27 | return res; 28 | } 29 | 30 | const strIncludesCodec = value => 31 | value && Object.values(VIDEO_CODEC).some(codec => value.includes(codec)); 32 | 33 | const hasCodecTrans = transformations => 34 | ['video_codec', 'streaming_profile'].some(key => isKeyInTransformation(transformations, key)); 35 | 36 | export const hasCodec = (transformations) => { 37 | if (!transformations) { 38 | return false; 39 | } 40 | 41 | if (isString(transformations)) { 42 | return strIncludesCodec(transformations); 43 | } 44 | 45 | if (hasCodecTrans(transformations)) { 46 | return true; 47 | } 48 | 49 | return !!transformations.some?.(transformation => { 50 | return hasCodec(transformation); 51 | }); 52 | }; 53 | -------------------------------------------------------------------------------- /src/plugins/colors/index.js: -------------------------------------------------------------------------------- 1 | export default async function lazyColorsPlugin(options) { 2 | const player = this; 3 | try { 4 | const { default: colorsPlugin } = await import(/* webpackChunkName: "colors" */ './colors'); 5 | const colors = colorsPlugin(player, options); 6 | player.cloudinary.colors = colors; 7 | return colors; 8 | } catch (error) { 9 | console.error('Failed to load colors plugin:', error); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/plugins/context-menu/components/context-menu-item.js: -------------------------------------------------------------------------------- 1 | import videojs from 'video.js'; 2 | import { createElement } from 'utils/dom'; 3 | 4 | const MenuItem = videojs.getComponent('MenuItem'); 5 | 6 | class ContextMenuItem extends MenuItem { 7 | handleClick() { 8 | super.handleClick(); 9 | this.options_.listener(); 10 | } 11 | 12 | createEl() { 13 | const label = createElement('span', { 14 | class: 'vjs-menu-item-text' + (this.options_.class ? ` ${this.options_.class}` : '') 15 | }); 16 | label.appendChild(document.createTextNode(this.localize(this.options_.label))); 17 | 18 | const el = createElement('li', { 19 | class: 'vjs-menu-item', 20 | tabIndex: -1 21 | }, label); 22 | 23 | return el; 24 | } 25 | } 26 | 27 | export default ContextMenuItem; 28 | -------------------------------------------------------------------------------- /src/plugins/context-menu/components/context-menu.js: -------------------------------------------------------------------------------- 1 | import videojs from 'video.js'; 2 | import isFunction from 'lodash/isFunction'; 3 | import ContextMenuItem from './context-menu-item'; 4 | import { setPosition } from 'utils/positioning'; 5 | 6 | const Menu = videojs.getComponent('Menu'); 7 | 8 | class ContextMenu extends Menu { 9 | 10 | constructor(player, options) { 11 | super(player, options); 12 | 13 | options.content.forEach(c => { 14 | let fn = null; 15 | 16 | if (isFunction(c.listener)) { 17 | fn = c.listener; 18 | } else if (typeof c.href === 'string') { 19 | fn = () => window.open(c.href); 20 | } else { 21 | fn = () => true; 22 | } 23 | 24 | this.addItem(new ContextMenuItem(player, { 25 | label: c.label, 26 | class: c.class, 27 | listener: (...args) => { 28 | fn(...args); 29 | this.dispose(); 30 | } 31 | })); 32 | }); 33 | } 34 | 35 | setPosition(left, top) { 36 | setPosition(this.el(), left, top); 37 | } 38 | 39 | createEl() { 40 | const el = super.createEl(); 41 | 42 | videojs.dom.addClass(el, 'vjs-context-menu-ui'); 43 | 44 | if (this.options_.position) { 45 | const { left, top } = this.options_.position; 46 | this.setPosition(left, top); 47 | } 48 | 49 | return el; 50 | } 51 | } 52 | 53 | export default ContextMenu; 54 | -------------------------------------------------------------------------------- /src/plugins/context-menu/context-menu.scss: -------------------------------------------------------------------------------- 1 | .vjs-context-menu-ui { 2 | position: absolute; 3 | z-index: 2; 4 | 5 | .vjs-menu-content { 6 | background: rgba(0,0,0,.6); 7 | border-radius: 0.2em; 8 | padding: 0; 9 | } 10 | 11 | .vjs-menu-item { 12 | // Override video.js styles for menus 13 | font-size: 1em; 14 | line-height: 1em; 15 | text-transform: none; 16 | 17 | cursor: pointer; 18 | margin: 0; 19 | padding: 0.8em 1.4em; 20 | 21 | &:active, &:hover { 22 | background-color: rgba(0, 0, 0, 0.5); 23 | } 24 | } 25 | 26 | .player-version { 27 | font-size: 80%; 28 | opacity: .7; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/plugins/context-menu/contextMenuContent.js: -------------------------------------------------------------------------------- 1 | const contextMenuContent = (player) => { 2 | 3 | const isLooping = player.loop(); 4 | const isPaused = player.paused(); 5 | const isMuted = player.muted(); 6 | const isFullscreen = player.isFullscreen(); 7 | 8 | const aboutMenuItem = { 9 | class: 'player-version', 10 | label: 'Cloudinary Player v' + VERSION 11 | }; 12 | 13 | if (!player.controls()) { 14 | return [aboutMenuItem]; 15 | } 16 | 17 | return [ 18 | { 19 | label: isLooping ? 'Unloop' : 'Loop', 20 | listener: () => { 21 | player.loop(!isLooping); 22 | } 23 | }, 24 | { 25 | label: isPaused ? 'Play' : 'Pause', 26 | listener: () => { 27 | if (isPaused) { 28 | player.play(); 29 | } else { 30 | player.pause(); 31 | } 32 | } 33 | }, 34 | { 35 | label: isMuted ? 'Unmute' : 'Mute', 36 | listener: () => { 37 | player.muted(!isMuted); 38 | } 39 | }, 40 | { 41 | label: isFullscreen ? 'Exit Fullscreen' : 'Fullscreen', 42 | listener: () => { 43 | if (isFullscreen) { 44 | player.exitFullscreen(); 45 | } else { 46 | player.requestFullscreen(); 47 | } 48 | } 49 | }, 50 | aboutMenuItem 51 | ]; 52 | }; 53 | 54 | export default contextMenuContent; 55 | -------------------------------------------------------------------------------- /src/plugins/ima/ima.js: -------------------------------------------------------------------------------- 1 | import 'videojs-contrib-ads'; 2 | import 'videojs-ima'; 3 | import 'videojs-ima/dist/videojs.ima.scss'; 4 | -------------------------------------------------------------------------------- /src/plugins/index.js: -------------------------------------------------------------------------------- 1 | import 'videojs-per-source-behaviors'; 2 | 3 | import aiHighlightsGraph from './ai-highlights-graph'; 4 | import analytics from './analytics'; 5 | import autoplayOnScroll from './autoplay-on-scroll'; 6 | import cloudinary from './cloudinary'; 7 | import cloudinaryAnalytics from './cloudinary-analytics'; 8 | import contextMenu from './context-menu'; 9 | import floatingPlayer from './floating-player'; 10 | import pacedTranscript from './paced-transcript'; 11 | import styledTextTracks from './styled-text-tracks'; 12 | import vttThumbnails from './vtt-thumbnails'; 13 | 14 | // Lazy loaded plugins 15 | import chapters from './chapters'; 16 | import colors from './colors'; 17 | import imaPlugin from './ima'; 18 | import interactionAreas from './interaction-areas'; 19 | import playlist from './playlist'; 20 | import shoppable from './shoppable-plugin'; 21 | import srtTextTracks from './srt-text-tracks'; 22 | import visualSearch from './visual-search'; 23 | import adaptiveStreaming from './adaptive-streaming'; 24 | 25 | const plugins = { 26 | aiHighlightsGraph, 27 | analytics, 28 | autoplayOnScroll, 29 | cloudinary, 30 | cloudinaryAnalytics, 31 | contextMenu, 32 | floatingPlayer, 33 | pacedTranscript, 34 | styledTextTracks, 35 | vttThumbnails, 36 | 37 | // Lazy loaded plugins 38 | chapters, 39 | colors, 40 | imaPlugin, 41 | playlist, 42 | shoppable, 43 | srtTextTracks, 44 | interactionAreas, 45 | visualSearch, 46 | adaptiveStreaming 47 | }; 48 | 49 | export default plugins; 50 | -------------------------------------------------------------------------------- /src/plugins/interaction-areas/index.js: -------------------------------------------------------------------------------- 1 | export default async function lazyInteractionAreasPlugin(player, playerOptions, videojsOptions) { 2 | try { 3 | const { interactionAreasService } = await import(/* webpackChunkName: "interaction-areas" */ './interaction-areas.service'); 4 | interactionAreasService(player, playerOptions, videojsOptions); 5 | } catch (error) { 6 | console.error('Failed to load plugin:', error); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/plugins/interaction-areas/interaction-areas.const.js: -------------------------------------------------------------------------------- 1 | export const INTERACTION_AREA_LAYOUT_LOCAL_STORAGE_NAME = 'cld-ia-layout-state'; 2 | 3 | export const INTERACTION_AREAS_PREFIX = 'vp-ia'; 4 | 5 | export const INTERACTION_AREAS_CONTAINER_CLASS_NAME = 'interaction-areas-container'; 6 | 7 | export const INTERACTION_AREAS_TEMPLATE = { 8 | PORTRAIT: 'portrait', 9 | LANDSCAPE: 'landscape', 10 | All: 'all', 11 | CENTER: 'center' 12 | }; 13 | 14 | export const INTERACTION_AREAS_THEME = { 15 | PULSING: 'pulsing', 16 | SHADOWED: 'shadowed' 17 | }; 18 | 19 | export const TEMPLATE_INTERACTION_AREAS_VTT = { 20 | [INTERACTION_AREAS_TEMPLATE.PORTRAIT]: 'https://res.cloudinary.com/prod/raw/upload/v1623772481/video-player/vtts/portrait.vtt', 21 | [INTERACTION_AREAS_TEMPLATE.LANDSCAPE]: 'https://res.cloudinary.com/prod/raw/upload/v1623772303/video-player/vtts/landscape.vtt', 22 | [INTERACTION_AREAS_TEMPLATE.All]: 'https://res.cloudinary.com/prod/raw/upload/v1623250266/video-player/vtts/all.vtt', 23 | [INTERACTION_AREAS_TEMPLATE.CENTER]: 'https://res.cloudinary.com/prod/raw/upload/v1623250265/video-player/vtts/center.vtt' 24 | }; 25 | 26 | export const INTERACTION_AREA_HAND_ICON = 'https://res.cloudinary.com/prod/image/upload/v1626764643/video-player/interaction-area-hand.svg'; 27 | 28 | export const CLOSE_INTERACTION_AREA_LAYOUT_DELAY = 4500; 29 | 30 | export const DEFAULT_INTERACTION_ARE_TRANSITION = 250; 31 | -------------------------------------------------------------------------------- /src/plugins/playlist/index.js: -------------------------------------------------------------------------------- 1 | export default async function lazyPlugin(options) { 2 | const player = this; 3 | try { 4 | const { default: playlistPlugin } = await import(/* webpackChunkName: "playlist" */ './playlist'); 5 | const playlist = playlistPlugin(player, options); 6 | player.cloudinary.playlist = playlist; 7 | return playlist; 8 | } catch (error) { 9 | console.error('Failed to load plugin:', error); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/plugins/playlist/ui/components/playlist-button.js: -------------------------------------------------------------------------------- 1 | import videojs from 'video.js'; 2 | 3 | // Get the ClickableComponent base class from Video.js 4 | const ClickableComponent = videojs.getComponent('ClickableComponent'); 5 | 6 | // Create a common class for playlist buttons 7 | class PlaylistButton extends ClickableComponent { 8 | 9 | constructor(player, options) { 10 | // It is important to invoke the superclass before anything else, 11 | // to get all the features of components out of the box! 12 | super(player, options); 13 | 14 | const type = options.type; 15 | 16 | if (!type && type !== 'previous' && type !== 'next') { 17 | throw new Error('Type must be either \'previous\' or \'next\''); 18 | } 19 | } 20 | 21 | // The `createEl` function of a component creates its DOM element. 22 | createEl() { 23 | const type = this.options_.type; 24 | const typeCssClass = `vjs-icon-${type}-item`; 25 | 26 | return videojs.dom.createEl('button', { 27 | // Prefixing classes of elements within a player with "vjs-" 28 | // is a convention used in Video.js. 29 | className: `vjs-control vjs-playlist-button vjs-button ${typeCssClass}`, 30 | ariaLabel: `Playlist ${type} item` 31 | }); 32 | } 33 | } 34 | 35 | export default PlaylistButton; 36 | -------------------------------------------------------------------------------- /src/plugins/playlist/ui/components/playlist-buttons.js: -------------------------------------------------------------------------------- 1 | import PlaylistNextButton from './playlist-next-button'; 2 | import PlaylistPreviousButton from './playlist-previous-button'; 3 | import './playlist-buttons.scss'; 4 | 5 | export { PlaylistNextButton, PlaylistPreviousButton }; 6 | -------------------------------------------------------------------------------- /src/plugins/playlist/ui/components/playlist-buttons.scss: -------------------------------------------------------------------------------- 1 | .cld-video-player { 2 | 3 | .vjs-playlist-button { 4 | display: none; 5 | } 6 | 7 | &.vjs-playlist { 8 | 9 | .vjs-playlist-button { 10 | display: block; 11 | } 12 | } 13 | 14 | } 15 | 16 | -------------------------------------------------------------------------------- /src/plugins/playlist/ui/components/playlist-next-button.js: -------------------------------------------------------------------------------- 1 | import PlaylistButton from './playlist-button'; 2 | import videojs from 'video.js'; 3 | 4 | class PlaylistNextButton extends PlaylistButton { 5 | 6 | constructor(player) { 7 | super(player, { type: 'next' }); 8 | } 9 | 10 | handleClick(event) { 11 | event.stopPropagation(); 12 | super.handleClick(event); 13 | this.player().cloudinary.playlist().playNext(); 14 | } 15 | } 16 | 17 | videojs.registerComponent('PlaylistNextButton', PlaylistNextButton); 18 | 19 | export default PlaylistNextButton; 20 | -------------------------------------------------------------------------------- /src/plugins/playlist/ui/components/playlist-previous-button.js: -------------------------------------------------------------------------------- 1 | import PlaylistButton from './playlist-button'; 2 | import videojs from 'video.js'; 3 | 4 | class PlaylistPreviousButton extends PlaylistButton { 5 | 6 | constructor(player) { 7 | super(player, { type: 'previous' }); 8 | } 9 | 10 | handleClick(event) { 11 | super.handleClick(event); 12 | this.player().cloudinary.playlist().playPrevious(); 13 | } 14 | } 15 | 16 | videojs.registerComponent('PlaylistPreviousButton', PlaylistPreviousButton); 17 | 18 | export default PlaylistPreviousButton; 19 | -------------------------------------------------------------------------------- /src/plugins/playlist/ui/layout/playlist-layout-custom.js: -------------------------------------------------------------------------------- 1 | import PlaylistLayout from './playlist-layout'; 2 | 3 | class PlaylistLayoutCustom extends PlaylistLayout { 4 | 5 | getCls() { 6 | let cls = super.getCls(); 7 | cls.push('cld-plw-custom'); 8 | 9 | return cls; 10 | } 11 | 12 | createEl() { 13 | const el = super.createEl(); 14 | this.options_.renderTo.appendChild(el); 15 | 16 | return el; 17 | } 18 | } 19 | 20 | 21 | export default PlaylistLayoutCustom; 22 | -------------------------------------------------------------------------------- /src/plugins/playlist/ui/layout/playlist-layout-horizontal.js: -------------------------------------------------------------------------------- 1 | import PlaylistLayout from './playlist-layout'; 2 | 3 | class PlaylistLayoutHorizontal extends PlaylistLayout { 4 | constructor (player, options) { 5 | options.wrap = true; 6 | super(player, options); 7 | } 8 | 9 | getCls() { 10 | const cls = super.getCls(); 11 | cls.push('cld-plw-horizontal'); 12 | return cls; 13 | } 14 | } 15 | 16 | export default PlaylistLayoutHorizontal; 17 | -------------------------------------------------------------------------------- /src/plugins/playlist/ui/layout/playlist-layout-vertical.js: -------------------------------------------------------------------------------- 1 | import PlaylistLayout from './playlist-layout'; 2 | 3 | class PlaylistLayoutVertical extends PlaylistLayout { 4 | 5 | constructor (player, options) { 6 | options.wrap = true; 7 | super(player, options); 8 | } 9 | 10 | getCls() { 11 | const cls = super.getCls(); 12 | cls.push('cld-plw-vertical'); 13 | 14 | return cls; 15 | } 16 | } 17 | 18 | 19 | export default PlaylistLayoutVertical; 20 | -------------------------------------------------------------------------------- /src/plugins/playlist/ui/playlist.const.js: -------------------------------------------------------------------------------- 1 | export const DEFAULT_AUTO_ADVANCE = 0; 2 | 3 | export const DEFAULT_PRESENT_UPCOMING = 10; 4 | 5 | export const UPCOMING_VIDEO_TRANSITION = 1; 6 | 7 | export const PLAYLIST_DEFAULTS_OPTIONS = { 8 | fluid: false, 9 | show: true, 10 | direction: 'vertical', 11 | total: 4, 12 | selector: false, 13 | renderTo: [] 14 | }; 15 | -------------------------------------------------------------------------------- /src/plugins/playlist/ui/thumbnail/thumbnail.scss: -------------------------------------------------------------------------------- 1 | .cld-thumbnail { 2 | position: relative; 3 | display: block; 4 | width: 100%; 5 | overflow: hidden; 6 | font-size: 1em; 7 | text-align: left; 8 | background-repeat: no-repeat; 9 | background-size: cover; 10 | background-position: center; 11 | 12 | .cld-thumbnail-img { 13 | display: none; 14 | } 15 | 16 | &:before { 17 | content: ''; 18 | position: absolute; 19 | top: 40%; 20 | max-height: 60%; 21 | right: 0; 22 | bottom: 0; 23 | left: 0; 24 | background: linear-gradient(to top, var(--color-base), transparent 80%); 25 | opacity: 0.9; 26 | } 27 | 28 | &.cld-plw-panel-item-active { 29 | border: 1px solid var(--color-accent); 30 | box-sizing: border-box; 31 | box-shadow: 0 0 3em -0.5em var(--color-accent) inset; 32 | } 33 | } 34 | 35 | .cld-plw-panel-item:hover:after { 36 | content: ''; 37 | position: absolute; 38 | width: 100%; 39 | height: 100%; 40 | top: 0px; 41 | left: 0px; 42 | background-color: var(--color-text); 43 | opacity: 0.2; 44 | } 45 | 46 | @media only screen and (max-width: 768px) { 47 | .cld-thumbnail:before { 48 | background: none; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/plugins/playlist/utils/api.js: -------------------------------------------------------------------------------- 1 | import camelCase from 'lodash/camelCase'; 2 | import isPlainObject from 'lodash/isPlainObject'; 3 | import { parseISO8601 } from './time'; 4 | 5 | const TIME_FIELDS = ['created_at', 'updated_at']; 6 | 7 | const normalizeJsonResponse = (obj) => { 8 | const agg = {}; 9 | 10 | if (isPlainObject(obj)) { 11 | Object.keys(obj).reduce((agg, key) => { 12 | const newKey = camelCase(key); 13 | 14 | if (TIME_FIELDS.indexOf(key) !== -1) { 15 | agg[newKey] = new Date(parseISO8601(obj[key])); 16 | } else { 17 | agg[newKey] = normalizeJsonResponse(obj[key]); 18 | } 19 | 20 | return agg; 21 | }, agg); 22 | 23 | return agg; 24 | } else if (Array.isArray(obj)) { 25 | return obj.map((item) => normalizeJsonResponse(item)); 26 | } else { 27 | return obj; 28 | } 29 | }; 30 | 31 | 32 | export { normalizeJsonResponse }; 33 | -------------------------------------------------------------------------------- /src/plugins/playlist/utils/dom.js: -------------------------------------------------------------------------------- 1 | export const wrap = (el, wrapper) => { 2 | el.parentNode.insertBefore(wrapper, el); 3 | wrapper.appendChild(el); 4 | 5 | return wrapper; 6 | }; 7 | -------------------------------------------------------------------------------- /src/plugins/playlist/utils/time.js: -------------------------------------------------------------------------------- 1 | // https://github.com/csnover/js-iso8601/blob/master/iso8601.js 2 | const numericKeys = [1, 4, 5, 6, 7, 10, 11]; 3 | 4 | const parseISO8601 = function (date) { 5 | let timestamp = 0; 6 | let struct = 0; 7 | let minutesOffset = 0; 8 | 9 | // ES5 §15.9.4.2 states that the string should attempt to be parsed as a Date Time String Format string 10 | // before falling back to any implementation-specific date parsing, so that’s what we do, even if native 11 | // implementations could be faster 12 | // 1 YYYY 2 MM 3 DD 4 HH 5 mm 6 ss 7 msec 8 Z 9 ± 10 tzHH 11 tzmm 13 | if ((struct = /^(\d{4}|[+\-]\d{6})(?:-(\d{2})(?:-(\d{2}))?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3}))?)?(?:(Z)|([+\-])(\d{2})(?::(\d{2}))?)?)?$/.exec(date))) { 14 | // avoid NaN timestamps caused by “undefined” values being passed to Date.UTC 15 | for (let i = 0, k; (k = numericKeys[i]); ++i) { 16 | struct[k] = +struct[k] || 0; 17 | } 18 | 19 | // allow undefined days and months 20 | struct[2] = (+struct[2] || 1) - 1; 21 | struct[3] = +struct[3] || 1; 22 | 23 | if (struct[8] !== 'Z' && struct[9] !== undefined) { 24 | minutesOffset = struct[10] * 60 + struct[11]; 25 | 26 | if (struct[9] === '+') { 27 | minutesOffset = 0 - minutesOffset; 28 | } 29 | } 30 | 31 | timestamp = Date.UTC(struct[1], struct[2], struct[3], struct[4], struct[5] + minutesOffset, struct[6], struct[7]); 32 | } else { 33 | timestamp = NaN; 34 | } 35 | 36 | return timestamp; 37 | }; 38 | 39 | export { parseISO8601 }; 40 | -------------------------------------------------------------------------------- /src/plugins/shoppable-plugin/index.js: -------------------------------------------------------------------------------- 1 | export default async function lazyShoppablePlugin(player, options) { 2 | 3 | const { default: ShoppableWidget } = await import(/* webpackChunkName: "shoppable" */ '../../components/shoppable-bar/shoppable-widget'); 4 | new ShoppableWidget(player, options.shoppable).init(); 5 | 6 | } 7 | -------------------------------------------------------------------------------- /src/plugins/srt-text-tracks/index.js: -------------------------------------------------------------------------------- 1 | export default async function lazySrtTextTracksPlugin(config) { 2 | const player = this; 3 | try { 4 | const { default: srtTextTracks } = await import(/* webpackChunkName: "srt-text-tracks" */ './srt-text-tracks'); 5 | player.ready(() => srtTextTracks(config, player)); 6 | } catch (error) { 7 | console.error('Failed to load srt-text-tracks plugin:', error); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/plugins/styled-text-tracks/index.js: -------------------------------------------------------------------------------- 1 | import styledTextTracks from './styled-text-tracks'; 2 | 3 | export default async function styledTextTracksPlugin(config) { 4 | const player = this; 5 | try { 6 | player.ready(() => styledTextTracks(config, player)); 7 | } catch (error) { 8 | console.error('Failed to load plugin:', error); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/plugins/visual-search/components/SearchButton.js: -------------------------------------------------------------------------------- 1 | import videojs from 'video.js'; 2 | 3 | export const SearchButton = (onClick) => { 4 | const button = videojs.dom.createEl('button', { 5 | className: 'vjs-control vjs-button vjs-visual-search-button', 6 | title: 'Search video content', 7 | ariaLabel: 'Search video content' 8 | }); 9 | 10 | const searchIcon = videojs.dom.createEl('span', { 11 | className: 'vjs-icon-search' 12 | }); 13 | button.appendChild(searchIcon); 14 | 15 | const spinnerIcon = videojs.dom.createEl('span', { 16 | className: 'vjs-loading-spinner' 17 | }); 18 | button.appendChild(spinnerIcon); 19 | 20 | button.addEventListener('click', onClick); 21 | 22 | return button; 23 | }; 24 | -------------------------------------------------------------------------------- /src/plugins/visual-search/components/SearchInput.js: -------------------------------------------------------------------------------- 1 | import videojs from 'video.js'; 2 | 3 | export const SearchInput = (onSearch, onClose) => { 4 | const form = videojs.dom.createEl('form', { 5 | className: 'vjs-visual-search-form' 6 | }); 7 | 8 | const input = videojs.dom.createEl('input', { 9 | className: 'vjs-visual-search-input', 10 | type: 'text', 11 | ariaLabel: 'Search input', 12 | tabIndex: -1 13 | }); 14 | 15 | const closeButton = videojs.dom.createEl('button', { 16 | className: 'vjs-control vjs-button vjs-visual-search-close', 17 | type: 'button', 18 | title: 'Close search', 19 | ariaLabel: 'Close search', 20 | tabIndex: -1 21 | }); 22 | 23 | // Add close icon 24 | const closeIcon = videojs.dom.createEl('span', { 25 | className: 'vjs-icon-close' 26 | }); 27 | closeButton.appendChild(closeIcon); 28 | 29 | form.appendChild(input); 30 | form.appendChild(closeButton); 31 | 32 | // Handle search submission 33 | form.addEventListener('submit', (e) => { 34 | e.preventDefault(); 35 | const query = input.value.trim(); 36 | if (query) { 37 | onSearch(query); 38 | } 39 | }); 40 | 41 | // Handle close button 42 | closeButton.addEventListener('click', (e) => { 43 | e.preventDefault(); 44 | if (onClose) { 45 | onClose(); 46 | } 47 | }); 48 | 49 | return { 50 | element: form, 51 | input, 52 | closeButton 53 | }; 54 | }; 55 | -------------------------------------------------------------------------------- /src/plugins/visual-search/index.js: -------------------------------------------------------------------------------- 1 | export default async function lazyVisualSearchPlugin(options) { 2 | const player = this; 3 | try { 4 | const { default: initPlugin } = await import(/* webpackChunkName: "visual-search" */ './visual-search'); 5 | player.ready(() => initPlugin(options, player)); 6 | } catch (error) { 7 | console.error('Failed to load plugin:', error); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/plugins/vtt-thumbnails/vtt-thumbnails.scss: -------------------------------------------------------------------------------- 1 | .cld-video-player { 2 | .vjs-vtt-thumbnail-display { 3 | position: absolute; 4 | left: 0; 5 | z-index: 1; 6 | transition: opacity 0.2s; 7 | bottom: 3em; 8 | pointer-events: none; 9 | border: 1px solid var(--color-base); 10 | border-radius: 2px; 11 | box-shadow: 0 0 6px rgba(0, 0, 0, 0.3); 12 | backdrop-filter: blur(12px); 13 | transform: translateX(-50%); 14 | } 15 | 16 | .vjs-vtt-time-display { 17 | font-size: 80%; 18 | line-height: 1.4; 19 | position: absolute; 20 | bottom: -2.8em; 21 | left: 0; 22 | right: 0; 23 | margin: auto; 24 | color: var(--color-text); 25 | background: var(--color-base); 26 | padding: 0.3em 0.6em; 27 | width: fit-content; 28 | border-radius: 4px; 29 | } 30 | 31 | .vjs-time-tooltip { 32 | right: auto !important; 33 | translate: -50%; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/utils/apply-with-props.js: -------------------------------------------------------------------------------- 1 | export function applyWithProps(context, obj) { 2 | Object.entries(obj).forEach(([key, value]) => { 3 | if (context[key] && typeof context[key] === 'function') { 4 | context[key](value); 5 | } 6 | }); 7 | } 8 | -------------------------------------------------------------------------------- /src/utils/consts.js: -------------------------------------------------------------------------------- 1 | export const PLAYER_EVENT = { 2 | READY: 'ready', 3 | PLAY: 'play', 4 | PLAYING: 'playing', 5 | PAUSE: 'pause', 6 | SEEK: 'seek', 7 | SEEKING: 'seeking', 8 | MUTE: 'mute', 9 | UNMUTE: 'unmute', 10 | PAUSE_NO_SEEK: 'pausenoseek', 11 | ERROR: 'error', 12 | TIME_UPDATE: 'timeupdate', 13 | EMPTIED: 'emptied', 14 | RETRY_PLAYLIST: 'retryplaylist', 15 | CAN_PLAY_THROUGH: 'canplaythrough', 16 | CLD_SOURCE_CHANGED: 'cldsourcechanged', 17 | SOURCE_CHANGED: 'sourcechanged', 18 | LOADED_METADATA: 'loadedmetadata', 19 | LOADED_DATA: 'loadeddata', 20 | REFRESH_TEXT_TRACKS: 'refreshTextTracks', 21 | PLAYLIST_CREATED: 'playlistcreated', 22 | UP_COMING_VIDEO_SHOW: 'upcomingvideoshow', 23 | UP_COMING_VIDEO_HIDE: 'upcomingvideohide', 24 | PLAYLIST_ITEM_CHANGED: 'playlistitemchanged', 25 | VOLUME_CHANGE: 'volumechange', 26 | FLUID: 'fluid', 27 | PLAYLIST_PANEL: 'PlaylistPanel', 28 | ENDED: 'ended', 29 | RESIZE: 'resize', 30 | START: 'start', 31 | VIDEO_LOAD: 'videoload', 32 | PRODUCT_BAR_MIN: 'productBarMin', 33 | SHOW_PRODUCTS_OVERLAY: 'showProductsOverlay', 34 | SHOPPABLE_ITEM_CHANGED: 'shoppableitemchanged', 35 | FULL_SCREEN_CHANGE: 'fullscreenchange', 36 | PERCENTS_PLAYED: 'percentsplayed', 37 | TIME_PLAYED: 'timeplayed', 38 | PLAYER_LOAD: 'playerload', 39 | DISPOSE: 'dispose', 40 | QUALITY_CHANGED: 'qualitychanged' 41 | }; 42 | 43 | export const SOURCE_TYPE = { 44 | AUDIO: 'AudioSource', 45 | VIDEO: 'VideoSource' 46 | }; 47 | -------------------------------------------------------------------------------- /src/utils/css-prefix.js: -------------------------------------------------------------------------------- 1 | import defaults from '../config/defaults'; 2 | import { find } from './find'; 3 | 4 | const CLASS_PREFIX = 'cld-video-player'; 5 | const SKIN_CLASS_PREFIX = `${CLASS_PREFIX}-skin-`; 6 | 7 | const playerClassPrefix = (componentInstance) => `${CLASS_PREFIX}-${componentInstance.id_}`; 8 | 9 | const skinClass = (skin) => `${SKIN_CLASS_PREFIX}${skin}`; 10 | 11 | const skinClassPrefix = (componentInstance) => find(componentInstance.el().classList, (cls) => cls.startsWith(SKIN_CLASS_PREFIX)); 12 | 13 | const setSkinClassPrefix = (componentInstance, name) => { 14 | const currentSkinPrefix = skinClassPrefix(componentInstance); 15 | const skinName = name ? name.replace(SKIN_CLASS_PREFIX, '') : false; 16 | 17 | let newSkinPrefix = ''; 18 | if (skinName) { 19 | // From html class 20 | newSkinPrefix = skinClass(skinName); 21 | } else if (componentInstance.options_.skin) { 22 | // From JS config 23 | newSkinPrefix = skinClass(componentInstance.options_.skin); 24 | } else { 25 | // Defult 26 | newSkinPrefix = skinClass(defaults.skin); 27 | } 28 | 29 | if (newSkinPrefix !== currentSkinPrefix) { 30 | if (currentSkinPrefix) { 31 | componentInstance.removeClass(currentSkinPrefix); 32 | } 33 | componentInstance.addClass(newSkinPrefix); 34 | } 35 | 36 | if (skinName && componentInstance.options_.skin !== skinName) { 37 | componentInstance.options_.skin = skinName; 38 | } 39 | 40 | }; 41 | 42 | export { CLASS_PREFIX, playerClassPrefix, skinClassPrefix, skinClass, setSkinClassPrefix }; 43 | -------------------------------------------------------------------------------- /src/utils/find.js: -------------------------------------------------------------------------------- 1 | function find(list, callback) { 2 | if (Array.prototype.find && Array.isArray(list)) { 3 | return list.find(callback); 4 | } 5 | 6 | return findElementAndIndex(list, callback)[0]; 7 | } 8 | 9 | function findElementAndIndex(list, callback) { 10 | for (let i = 0; i < list.length; i++) { 11 | const element = list[i]; 12 | if (callback(element, i, list)) { 13 | return [element, i]; 14 | } 15 | } 16 | 17 | return [undefined, -1]; 18 | } 19 | 20 | export { find }; 21 | -------------------------------------------------------------------------------- /src/utils/fontFace.js: -------------------------------------------------------------------------------- 1 | import WebFont from 'webfontloader'; 2 | 3 | const FONT_FAMILY = 'Inter'; 4 | 5 | const fontFace = (elem, fontFace) => { 6 | // Default font-face is "Inter" 7 | if (typeof fontFace === 'undefined') { 8 | fontFace = FONT_FAMILY; 9 | } 10 | 11 | if (fontFace && fontFace !== 'inherit') { 12 | WebFont.load({ 13 | google: { 14 | families: [fontFace] 15 | } 16 | }); 17 | elem.style.fontFamily = fontFace; 18 | } else if (fontFace === 'inherit') { 19 | elem.style.fontFamily = 'inherit'; 20 | } 21 | }; 22 | 23 | export { fontFace }; 24 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | import * as slicing from './slicing'; 2 | import * as positioning from './positioning'; 3 | import * as cloudinaryUtils from './cloudinary'; 4 | import * as mixin from './mixin'; 5 | import * as fontFace from './fontFace'; 6 | import * as cssPrefix from './css-prefix'; 7 | import * as normalizeAttributes from './attributes-normalizer'; 8 | 9 | const Utils = Object.assign({}, 10 | slicing, 11 | positioning, 12 | cloudinaryUtils, 13 | fontFace, 14 | mixin, 15 | cssPrefix, 16 | normalizeAttributes 17 | ); 18 | 19 | export default Utils; 20 | -------------------------------------------------------------------------------- /src/utils/mixin.js: -------------------------------------------------------------------------------- 1 | function mixin(...mixins) { 2 | return mixins.reduce((c, mixin) => mixin(c), class Blank {}); 3 | } 4 | 5 | export { mixin }; 6 | -------------------------------------------------------------------------------- /src/utils/object.js: -------------------------------------------------------------------------------- 1 | import snakeCase from 'lodash/snakeCase'; 2 | 3 | export const convertKeysToSnakeCase = (obj) => { 4 | let snakeCaseObj = {}; 5 | 6 | for (const key of Object.keys(obj)) { 7 | const snakeCaseKey = snakeCase(key); 8 | snakeCaseObj[snakeCaseKey] = obj[key]; 9 | } 10 | 11 | return snakeCaseObj; 12 | }; 13 | -------------------------------------------------------------------------------- /src/utils/querystring.js: -------------------------------------------------------------------------------- 1 | const objectToQuerystring = (obj) => { 2 | const keys = Object.keys(obj); 3 | 4 | if (!keys.length) { 5 | return ''; 6 | } 7 | 8 | const query = keys.map((key) => `${key}=${obj[key]}`).join('&'); 9 | return `?${query}`; 10 | }; 11 | 12 | export { objectToQuerystring }; 13 | -------------------------------------------------------------------------------- /src/utils/slicing.js: -------------------------------------------------------------------------------- 1 | function _sliceProperties(obj, isUnset, ...props) { 2 | return props.reduce((acc, prop) => { 3 | if (Object.prototype.hasOwnProperty.call(obj, prop)) { 4 | acc[prop] = obj[prop]; 5 | if (isUnset) { 6 | delete obj[prop]; 7 | } 8 | } 9 | return acc; 10 | }, {}); 11 | } 12 | 13 | function sliceProperties(obj, ...props) { 14 | return _sliceProperties(obj, false, ...props); 15 | } 16 | 17 | function sliceAndUnsetProperties(obj, ...props) { 18 | return _sliceProperties(obj, true, ...props); 19 | } 20 | 21 | export { sliceProperties, sliceAndUnsetProperties }; 22 | -------------------------------------------------------------------------------- /src/utils/time.js: -------------------------------------------------------------------------------- 1 | // Convert time string i.e. '2:40' to seconds number (160) 2 | // Also allows h:m:s format and mm:ss, m:s etc. 3 | const parseTime = function (hms) { 4 | const [seconds, minutes, hours] = hms.split(':').reverse(); 5 | let sum = null; 6 | if (!isNaN(seconds)) { 7 | sum = (+hours || 0) * 60 * 60 + (+minutes || 0) * 60 + (+seconds); 8 | } 9 | return sum; 10 | }; 11 | 12 | export { parseTime }; 13 | -------------------------------------------------------------------------------- /src/utils/video-retry.js: -------------------------------------------------------------------------------- 1 | const checkIfVideoIsAvailable = (videoUrl, videoType = 'default') => { 2 | return new Promise((resolve, reject) => { 3 | const tempVideo = document.createElement('video'); 4 | tempVideo.setAttribute('crossorigin', 'anonymous'); 5 | const targetEvent = videoType === 'live' ? 'onprogress' : 'canplay'; 6 | tempVideo[targetEvent] = () => { 7 | tempVideo.onerror = null; 8 | tempVideo[targetEvent] = null; 9 | resolve(); 10 | }; 11 | tempVideo.onerror = () => reject(); 12 | tempVideo.src = videoUrl; 13 | tempVideo.load(); 14 | }); 15 | }; 16 | 17 | const isVideoInReadyState = (readyState) => { 18 | return readyState >= (/iPad|iPhone|iPod/.test(navigator.userAgent) ? 1 : 4); 19 | }; 20 | 21 | export { checkIfVideoIsAvailable, isVideoInReadyState }; 22 | -------------------------------------------------------------------------------- /test/adaptive-streaming.test.js: -------------------------------------------------------------------------------- 1 | describe('Adaptive streaming tests', () => { 2 | beforeAll(async () => { 3 | jest.setTimeout(35000); 4 | await page.setViewport({ width: 1280, height: 800 }); 5 | await page.goto('http://localhost:3000/docs/adaptive-streaming.html', { waitUntil: 'load' }); 6 | await page.evaluate(() => { 7 | Object.defineProperty(HTMLMediaElement.prototype, 'playing', { 8 | get: function () { 9 | return !!(this.currentTime > 0 && !this.paused && !this.ended && 10 | this.readyState > 2); 11 | } 12 | }); 13 | }); 14 | }, 10000); 15 | it('Should not throw an error when setting new hls source', async () => { 16 | jest.setTimeout(35000); 17 | await page.waitFor(1000); 18 | 19 | // async function not working without being put inside a template string, 20 | // See https://github.com/puppeteer/puppeteer/issues/1665 21 | const error = await page.evaluate(`(async () => { 22 | let error = null; 23 | 24 | // Get any error into error variable 25 | playerHls.on('error', (e) => (error = e.Player.videojs.error())); 26 | 27 | // Set new hls source 28 | playerHls.source('snow_horses', {sourceTypes: ['hls'], transformation: { streaming_profile: 'hd' }}); 29 | 30 | // wait a second for error event 31 | await new Promise(resolve=>setTimeout(resolve, 1000)); 32 | 33 | // Return null or an error object 34 | return JSON.stringify(error); 35 | })()`); 36 | expect(JSON.parse(error)).toEqual(null); // expect no error 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test/ads.test.js: -------------------------------------------------------------------------------- 1 | describe('Ads tests', () => { 2 | 3 | beforeEach(async () => { 4 | await page.setViewport({ width: 1280, height: 800 }); 5 | await page.goto('http://localhost:3000/vast-vpaid.html', { waitUntil: 'load' }); 6 | await page.evaluate(() => { 7 | Object.defineProperty(HTMLMediaElement.prototype, 'playing', { 8 | get: function () { 9 | return !!(this.currentTime > 0 && !this.paused && !this.ended && 10 | this.readyState > 2); 11 | } 12 | }); 13 | }); 14 | 15 | await page.evaluate(() => (window.ev = [])); 16 | await page.evaluate(() => player.on('readyforpreroll', () => { 17 | window.ev.push('preroll'); 18 | })); 19 | await page.evaluate(() => player.on('readyforpostroll', () => { 20 | window.ev.push('postroll'); 21 | })); 22 | }, 10000); 23 | 24 | it('event test', async () => { 25 | jest.setTimeout(65000); 26 | await page.waitForSelector('#player_ima-ad-container', {visible: true}); 27 | await page.waitFor(1000); 28 | const duration = await page.evaluate(() => player.duration()); 29 | await page.waitFor(duration * 1000 + 1000); 30 | const events = await page.evaluate(() => window.ev); 31 | expect(events.includes('preroll')).toEqual(true); 32 | expect(events.includes('postroll')).toEqual(true); 33 | }); 34 | 35 | }); 36 | -------------------------------------------------------------------------------- /test/autoplay.scroll.test.js: -------------------------------------------------------------------------------- 1 | describe('Auto-play tests', () => { 2 | 3 | beforeEach(async () => { 4 | await page.setViewport({width: 1280, height: 800}); 5 | await page.goto('http://localhost:3000/autoplay-on-scroll.html', {waitUntil: 'load'}); 6 | await page.evaluate(() => { 7 | Object.defineProperty(HTMLMediaElement.prototype, 'playing', { 8 | get: function() { 9 | return !!(this.currentTime > 0 && !this.paused && !this.ended && 10 | this.readyState > 2); 11 | } 12 | }); 13 | }); 14 | }, 10000); 15 | 16 | it('Test scroll', async () => { 17 | const player = await page.$('#player_html5_api'); 18 | expect(await player.isIntersectingViewport()).toEqual(false); 19 | await player.tap(); 20 | await page.waitFor(1000); 21 | expect(await page.$eval('#player_html5_api', p => p.playing)).toEqual(true); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /test/components.test.js: -------------------------------------------------------------------------------- 1 | describe('Components tests', () => { 2 | 3 | beforeEach(async () => { 4 | await page.setViewport({width: 1280, height: 800}); 5 | await page.goto('http://localhost:3000/components.html', {waitUntil: 'load'}); 6 | await page.evaluate(() => { 7 | Object.defineProperty(HTMLMediaElement.prototype, 'playing', { 8 | get: function() { 9 | return !!(this.currentTime > 0 && !this.paused && !this.ended && 10 | this.readyState > 2); 11 | } 12 | }); 13 | }); 14 | }, 10000); 15 | 16 | it('Test components', async () => { 17 | page.waitFor(1000); 18 | expect(await page.$eval('.vjs-playlist-control.vjs-playlist-next-control', (b => b.tagName))).toEqual('BUTTON'); 19 | expect(await page.$eval('.vjs-playlist-control.vjs-playlist-previous-control', (b => b.tagName))).toEqual('BUTTON'); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /test/custom-error.test.js: -------------------------------------------------------------------------------- 1 | describe('custom error tests', () => { 2 | 3 | beforeAll(async () => { 4 | jest.setTimeout(35000); 5 | await page.setViewport({ width: 1280, height: 800 }); 6 | await page.goto('http://localhost:3000/docs/custom-cld-errors.html', { waitUntil: 'load' }); 7 | await page.evaluate(() => { 8 | Object.defineProperty(HTMLMediaElement.prototype, 'playing', { 9 | get: function() { 10 | return !!(this.currentTime > 0 && !this.paused && !this.ended && 11 | this.readyState > 2); 12 | } 13 | }); 14 | }); 15 | }, 10000); 16 | 17 | it('Test error', async () => { 18 | jest.setTimeout(35000); 19 | await page.waitFor(1000); 20 | // eslint-disable-next-line no-undef 21 | const errorMsg = await page.evaluate(() => player.videojs.error_.message); 22 | expect(errorMsg).toBe('My custom error message'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /test/e2e/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['../../.eslintrc.js', 'plugin:@typescript-eslint/recommended', 'prettier'], 3 | parser: '@typescript-eslint/parser', 4 | plugins: ['@typescript-eslint', 'prettier'], 5 | rules: { 6 | 'prettier/prettier': [ 7 | 'error', 8 | { 9 | endOfLine: 'auto', 10 | }, 11 | ], 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /test/e2e/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | /test-results/ 3 | /playwright-report/ 4 | /blob-report/ 5 | /playwright/.cache/ 6 | -------------------------------------------------------------------------------- /test/e2e/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 256, 3 | "semi": true, 4 | "singleQuote": true, 5 | "trailingComma": "es5", 6 | "bracketSpacing": true, 7 | "tabWidth": 4, 8 | "arrowParens": "always" 9 | } 10 | -------------------------------------------------------------------------------- /test/e2e/components/BaseComponent.ts: -------------------------------------------------------------------------------- 1 | import { Locator, Page } from '@playwright/test'; 2 | 3 | /** 4 | * Base component constructor interface 5 | * 6 | * selector is optional in order to allow default selector usage. 7 | * For example: 8 | * 9 | * constructor(dataProps: IBaseComponent) { 10 | * const baseComponentProps: IBaseComponent = dataProps; 11 | * baseComponentProps.selector = dataProps?.selector ?? "//*"; 12 | * super(baseComponentProps); 13 | * ... 14 | * } 15 | */ 16 | export interface IBaseComponent { 17 | page: Page; 18 | selector: string; 19 | parentSelector?: string; 20 | iframeSelector?: string; 21 | } 22 | /** 23 | * Base class for an POM component class 24 | * such as buttons, dropList, etc 25 | */ 26 | export class BaseComponent { 27 | get locator(): Locator { 28 | return this._locator; 29 | } 30 | 31 | get props(): IBaseComponent { 32 | return this._props; 33 | } 34 | 35 | private readonly _locator: Locator; 36 | private readonly _props: IBaseComponent; 37 | 38 | constructor(basePageProps: IBaseComponent) { 39 | if (!basePageProps.selector) { 40 | throw Error(`Missing selector in basePageProps`); 41 | } 42 | const elementSelector: string = basePageProps.parentSelector ? `${basePageProps.parentSelector}${basePageProps.selector}` : basePageProps.selector; 43 | 44 | this._props = basePageProps; 45 | this._locator = basePageProps.iframeSelector ? basePageProps.page.frameLocator(basePageProps.iframeSelector).locator(elementSelector) : basePageProps.page.locator(elementSelector); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /test/e2e/fixtures/vpTest.ts: -------------------------------------------------------------------------------- 1 | import { ConsoleMessage, test } from '@playwright/test'; 2 | import { MainPage } from '../src/pom/mainPage'; 3 | import PageManager from '../src/pom/PageManager'; 4 | 5 | /** 6 | * Fixture parameters. 7 | */ 8 | type FixtureParams = { 9 | consoleErrors: ConsoleMessage[]; 10 | vpExamples: MainPage; 11 | pomPages: PageManager; 12 | }; 13 | 14 | /** 15 | * Extend Playwright test with custom fixtures. 16 | */ 17 | export const vpTest = test.extend({ 18 | /** 19 | * Page Manager 20 | */ 21 | pomPages: [ 22 | async ({ page }, use) => { 23 | const pomPages = new PageManager(page); 24 | await pomPages.mainPage.goto(); 25 | await use(pomPages); 26 | }, 27 | { scope: 'test', auto: true }, 28 | ], 29 | 30 | /** 31 | * Fixture for capturing console errors. 32 | */ 33 | consoleErrors: [ 34 | async ({ page }, use) => { 35 | const consoleLogs = new Array(); 36 | page.on('console', (msg) => { 37 | if (msg.type() === 'error') consoleLogs.push(msg); 38 | }); 39 | await use(consoleLogs); 40 | }, 41 | { scope: 'test' }, 42 | ], 43 | }); 44 | -------------------------------------------------------------------------------- /test/e2e/specs/ESM/esmAnalyticsPage.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 3 | import { getEsmLinkByName } from '../../testData/esmPageLinksData'; 4 | import { test } from '@playwright/test'; 5 | import { waitForPageToLoadWithTimeout } from '../../src/helpers/waitForPageToLoadWithTimeout'; 6 | import { ESM_URL } from '../../testData/esmUrl'; 7 | 8 | const link = getEsmLinkByName(ExampleLinkName.Analytics); 9 | 10 | vpTest(`Test if video on ESM analytics page is playing as expected`, async ({ page, pomPages }) => { 11 | await test.step('Navigate to ESM', async () => { 12 | await page.goto(ESM_URL); 13 | }); 14 | await test.step('Navigate to analytics page by clicking on link', async () => { 15 | await pomPages.mainPage.clickLinkByName(link.name); 16 | await waitForPageToLoadWithTimeout(page, 5000); 17 | }); 18 | await test.step('Click on play button of video player to play video', async () => { 19 | return pomPages.analyticsPage.analyticsVideoComponent.clickPlay(); 20 | }); 21 | await test.step('Validating that the video is playing', async () => { 22 | await pomPages.analyticsPage.analyticsVideoComponent.validateVideoIsPlaying(true); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /test/e2e/specs/ESM/esmApiAndEventsPage.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { ESM_URL } from '../../testData/esmUrl'; 3 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 4 | import { getEsmLinkByName } from '../../testData/esmPageLinksData'; 5 | import { testApiAndEventsPageVideoIsPlaying } from '../commonSpecs/apiAndEventsPageVideoPlaying'; 6 | 7 | const link = getEsmLinkByName(ExampleLinkName.APIAndEvents); 8 | 9 | vpTest(`Test if video on ESM API and Events page can play as expected`, async ({ page, pomPages }) => { 10 | await page.goto(ESM_URL); 11 | await testApiAndEventsPageVideoIsPlaying(page, pomPages, link); 12 | }); 13 | -------------------------------------------------------------------------------- /test/e2e/specs/ESM/esmAudioPlayerPage.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { ESM_URL } from '../../testData/esmUrl'; 3 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 4 | import { getEsmLinkByName } from '../../testData/esmPageLinksData'; 5 | import { testAudioPlayerPageVideosArePlaying } from '../commonSpecs/audioPlayerPageVideoPlaying'; 6 | 7 | const link = getEsmLinkByName(ExampleLinkName.AudioPlayer); 8 | 9 | vpTest(`Test if 2 videos on ESM audio player page are playing as expected`, async ({ page, pomPages }) => { 10 | await page.goto(ESM_URL); 11 | await testAudioPlayerPageVideosArePlaying(page, pomPages, link); 12 | }); 13 | -------------------------------------------------------------------------------- /test/e2e/specs/ESM/esmAutoplayOnScroll.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { ESM_URL } from '../../testData/esmUrl'; 3 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 4 | import { getEsmLinkByName } from '../../testData/esmPageLinksData'; 5 | import { testAutoplayOnScrollPageVideoIsPlaying } from '../commonSpecs/autoplayOnScrollVideoPlaying'; 6 | 7 | const link = getEsmLinkByName(ExampleLinkName.AutoplayOnScroll); 8 | 9 | vpTest(`Test if video on ESM autoplay on scroll page is playing as expected`, async ({ page, pomPages }) => { 10 | await page.goto(ESM_URL); 11 | await testAutoplayOnScrollPageVideoIsPlaying(page, pomPages, link); 12 | }); 13 | -------------------------------------------------------------------------------- /test/e2e/specs/ESM/esmChaptersPage.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { ESM_URL } from '../../testData/esmUrl'; 3 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 4 | import { getEsmLinkByName } from '../../testData/esmPageLinksData'; 5 | import { testChaptersPageVideosArePlaying } from '../commonSpecs/chaptersPage'; 6 | 7 | const link = getEsmLinkByName(ExampleLinkName.Chapters); 8 | 9 | vpTest(`Test if 3 videos on ESM chapters page are playing as expected`, async ({ page, pomPages }) => { 10 | await page.goto(ESM_URL); 11 | await testChaptersPageVideosArePlaying(page, pomPages, link); 12 | }); 13 | -------------------------------------------------------------------------------- /test/e2e/specs/ESM/esmCldAnalyticsPage.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 3 | import { testCldAnalyticsPageVideoIsPlaying } from '../commonSpecs/cldAnalyticsPageVideoPlaying'; 4 | import { getEsmLinkByName } from '../../testData/esmPageLinksData'; 5 | import { ESM_URL } from '../../testData/esmUrl'; 6 | 7 | const link = getEsmLinkByName(ExampleLinkName.CloudinaryAnalytics); 8 | 9 | vpTest(`Test if 4 videos on ESM Cloudinary analytics page are playing as expected`, async ({ page, pomPages }) => { 10 | await page.goto(ESM_URL); 11 | await testCldAnalyticsPageVideoIsPlaying(page, pomPages, link); 12 | }); 13 | -------------------------------------------------------------------------------- /test/e2e/specs/ESM/esmCodecsAndFormatsPage.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 3 | import { getEsmLinkByName } from '../../testData/esmPageLinksData'; 4 | import { ESM_URL } from '../../testData/esmUrl'; 5 | import { testCodecsAndFormatsPageVideoIsPlaying } from '../commonSpecs/codecsAndFormatsVideoPlaying'; 6 | 7 | const link = getEsmLinkByName(ExampleLinkName.CodecsAndFormats); 8 | 9 | vpTest(`Test if 3 videos on ESM codecs and formats page are playing as expected`, async ({ page, pomPages }) => { 10 | await page.goto(ESM_URL); 11 | await testCodecsAndFormatsPageVideoIsPlaying(page, pomPages, link); 12 | }); 13 | -------------------------------------------------------------------------------- /test/e2e/specs/ESM/esmColorsApiPage.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 3 | import { getEsmLinkByName } from '../../testData/esmPageLinksData'; 4 | import { ESM_URL } from '../../testData/esmUrl'; 5 | import { testColorsApiPageVideoIsPlaying } from '../commonSpecs/colorsApiPageVideoPlaying'; 6 | 7 | const link = getEsmLinkByName(ExampleLinkName.ColorsAPI); 8 | 9 | vpTest(`Test if 3 videos on ESM colors API page are playing as expected`, async ({ page, pomPages }) => { 10 | await page.goto(ESM_URL); 11 | await testColorsApiPageVideoIsPlaying(page, pomPages, link); 12 | }); 13 | -------------------------------------------------------------------------------- /test/e2e/specs/ESM/esmComponentsPage.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 3 | import { getEsmLinkByName } from '../../testData/esmPageLinksData'; 4 | import { ESM_URL } from '../../testData/esmUrl'; 5 | import { testComponentsPageVideoIsPlaying } from '../commonSpecs/componentsPageVideoPlaying'; 6 | 7 | const link = getEsmLinkByName(ExampleLinkName.Components); 8 | 9 | vpTest(`Test if video on ESM components page is playing as expected`, async ({ page, pomPages }) => { 10 | await page.goto(ESM_URL); 11 | await testComponentsPageVideoIsPlaying(page, pomPages, link); 12 | }); 13 | -------------------------------------------------------------------------------- /test/e2e/specs/ESM/esmDisplayConfigurationPage.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 3 | import { getEsmLinkByName } from '../../testData/esmPageLinksData'; 4 | import { ESM_URL } from '../../testData/esmUrl'; 5 | import { testDisplayConfigurationPageVideoIsPlaying } from '../commonSpecs/displayConfigurationPageVideoPlaying'; 6 | 7 | const link = getEsmLinkByName(ExampleLinkName.DisplayConfigurations); 8 | 9 | vpTest(`Test if video on ESM display configurations page is playing as expected`, async ({ page, pomPages }) => { 10 | await page.goto(ESM_URL); 11 | await testDisplayConfigurationPageVideoIsPlaying(page, pomPages, link); 12 | }); 13 | -------------------------------------------------------------------------------- /test/e2e/specs/ESM/esmFloatingPlayer.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 3 | import { getEsmLinkByName } from '../../testData/esmPageLinksData'; 4 | import { ESM_URL } from '../../testData/esmUrl'; 5 | import { testFloatingPlayerPageVideoIsPlaying } from '../commonSpecs/floatingPlayerPageVideoPlaying'; 6 | 7 | const link = getEsmLinkByName(ExampleLinkName.FloatingPlayer); 8 | 9 | vpTest(`Test if video on ESM floating player page is playing as expected`, async ({ page, pomPages }) => { 10 | await page.goto(ESM_URL); 11 | await testFloatingPlayerPageVideoIsPlaying(page, pomPages, link); 12 | }); 13 | -------------------------------------------------------------------------------- /test/e2e/specs/ESM/esmFluidLayoutsPage.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { getLinkByName } from '../../testData/pageLinksData'; 3 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 4 | import { testFluidLayoutsPageVideoIsPlaying } from '../commonSpecs/fluidLayoutsPageVideoPlaying'; 5 | import { ESM_URL } from '../../testData/esmUrl'; 6 | 7 | const link = getLinkByName(ExampleLinkName.FluidLayouts); 8 | 9 | vpTest(`Test if video on ESM fluid layouts page is playing as expected`, async ({ page, pomPages }) => { 10 | await page.goto(ESM_URL); 11 | await testFluidLayoutsPageVideoIsPlaying(page, pomPages, link); 12 | }); 13 | -------------------------------------------------------------------------------- /test/e2e/specs/ESM/esmForceHlsSubtitlesPage.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 3 | import { testForceHlsSubtitlesPageVideoIsPlaying } from '../commonSpecs/forceHlsSubtitlesPageVideoPlaying'; 4 | import { getEsmLinkByName } from '../../testData/esmPageLinksData'; 5 | import { ESM_URL } from '../../testData/esmUrl'; 6 | 7 | const link = getEsmLinkByName(ExampleLinkName.ForceHLSSubtitles); 8 | 9 | vpTest(`Test if video on ESM force HLS subtitles page is playing as expected`, async ({ page, pomPages }) => { 10 | await page.goto(ESM_URL); 11 | await testForceHlsSubtitlesPageVideoIsPlaying(page, pomPages, link); 12 | }); 13 | -------------------------------------------------------------------------------- /test/e2e/specs/ESM/esmHighlightsGraphPage.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 3 | import { getEsmLinkByName } from '../../testData/esmPageLinksData'; 4 | import { test } from '@playwright/test'; 5 | import { waitForPageToLoadWithTimeout } from '../../src/helpers/waitForPageToLoadWithTimeout'; 6 | import { ESM_URL } from '../../testData/esmUrl'; 7 | 8 | const link = getEsmLinkByName(ExampleLinkName.AIHighlightsGraph); 9 | 10 | vpTest(`Test if video on ESM highlights graph page is playing as expected`, async ({ page, pomPages }) => { 11 | await test.step('Navigate to ESM', async () => { 12 | await page.goto(ESM_URL); 13 | }); 14 | await test.step('Navigate to highlights graph page by clicking on link', async () => { 15 | await pomPages.mainPage.clickLinkByName(link.name); 16 | await waitForPageToLoadWithTimeout(page, 5000); 17 | }); 18 | await test.step('Click on play button of video player to play video', async () => { 19 | return pomPages.highlightGraphPage.videoHighlightsGraphPage.clickPlay(); 20 | }); 21 | await test.step('Validating that the video is playing', async () => { 22 | await pomPages.highlightGraphPage.videoHighlightsGraphPage.validateVideoIsPlaying(true); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /test/e2e/specs/ESM/esmMainPageVideoIsPlaying.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { testMainPageVideoIsPlaying } from '../commonSpecs/mainPageVideoPlaying'; 3 | import { ESM_URL } from '../../testData/esmUrl'; 4 | 5 | /** 6 | * Testing if video on main page is playing by checking that is pause return false. 7 | * The video in the main page is not configured as autoplay so first need to click on play button. 8 | */ 9 | vpTest(`Test if video on ESM main page can play as expected`, async ({ page, pomPages }) => { 10 | await page.goto(ESM_URL); 11 | await testMainPageVideoIsPlaying(page, pomPages.mainPage.videoMainPage); 12 | }); 13 | -------------------------------------------------------------------------------- /test/e2e/specs/ESM/esmMultiplePlayersPage.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 3 | import { testMultiplePlayersPageVideoIsPlaying } from '../commonSpecs/multiplePlayersPageVideoPlaying'; 4 | import { getEsmLinkByName } from '../../testData/esmPageLinksData'; 5 | import { ESM_URL } from '../../testData/esmUrl'; 6 | 7 | const link = getEsmLinkByName(ExampleLinkName.MultiplePlayers); 8 | 9 | vpTest(`Test if 3 videos on ESM multiple players page are playing as expected`, async ({ page, pomPages }) => { 10 | await page.goto(ESM_URL); 11 | await testMultiplePlayersPageVideoIsPlaying(page, pomPages, link); 12 | }); 13 | -------------------------------------------------------------------------------- /test/e2e/specs/ESM/esmPlaylistByTag.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 3 | import { getEsmLinkByName } from '../../testData/esmPageLinksData'; 4 | import { testPlaylistByTagPageVideoIsPlaying } from '../commonSpecs/playlistByTagPageVideoPlaying'; 5 | import { ESM_URL } from '../../testData/esmUrl'; 6 | 7 | const link = getEsmLinkByName(ExampleLinkName.PlaylistByTag); 8 | 9 | vpTest(`Test if video on ESM playlist by tag page is playing as expected`, async ({ page, pomPages }) => { 10 | await page.goto(ESM_URL); 11 | await testPlaylistByTagPageVideoIsPlaying(page, pomPages, link); 12 | }); 13 | -------------------------------------------------------------------------------- /test/e2e/specs/ESM/esmPlaylistPage.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 3 | import { testPlaylistPageVideoIsPlaying } from '../commonSpecs/playlistPageVideoPlaying'; 4 | import { getEsmLinkByName } from '../../testData/esmPageLinksData'; 5 | import { ESM_URL } from '../../testData/esmUrl'; 6 | 7 | const link = getEsmLinkByName(ExampleLinkName.Playlist); 8 | 9 | vpTest(`Test if 2 videos on ESM playlist page are playing as expected`, async ({ page, pomPages }) => { 10 | await page.goto(ESM_URL); 11 | await testPlaylistPageVideoIsPlaying(page, pomPages, link); 12 | }); 13 | -------------------------------------------------------------------------------- /test/e2e/specs/ESM/esmPosterOptionsPage.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 3 | import { testPosterOptionsPageVideoIsPlaying } from '../commonSpecs/posterOptionsPage'; 4 | import { getEsmLinkByName } from '../../testData/esmPageLinksData'; 5 | import { ESM_URL } from '../../testData/esmUrl'; 6 | 7 | const link = getEsmLinkByName(ExampleLinkName.PosterOptions); 8 | 9 | vpTest(`Test if 4 videos on ESM poster options page are playing as expected`, async ({ page, pomPages }) => { 10 | await page.goto(ESM_URL); 11 | await testPosterOptionsPageVideoIsPlaying(page, pomPages, link); 12 | }); 13 | -------------------------------------------------------------------------------- /test/e2e/specs/ESM/esmProfilesPage.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 3 | import { testProfilesPageVideoIsPlaying } from '../commonSpecs/profilesPageVideoPlaying'; 4 | import { getEsmLinkByName } from '../../testData/esmPageLinksData'; 5 | import { ESM_URL } from '../../testData/esmUrl'; 6 | 7 | const link = getEsmLinkByName(ExampleLinkName.Profiles); 8 | 9 | vpTest(`Test if 3 videos on ESM profiles page are playing as expected`, async ({ page, pomPages }) => { 10 | await page.goto(ESM_URL); 11 | await testProfilesPageVideoIsPlaying(page, pomPages, link); 12 | }); 13 | -------------------------------------------------------------------------------- /test/e2e/specs/ESM/esmRawUrlPage.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 3 | import { testRawUrlPageVideoIsPlaying } from '../commonSpecs/rawUrlPageVideoPlaying'; 4 | import { getEsmLinkByName } from '../../testData/esmPageLinksData'; 5 | import { ESM_URL } from '../../testData/esmUrl'; 6 | 7 | const link = getEsmLinkByName(ExampleLinkName.RawURL); 8 | 9 | vpTest(`Test if 2 videos on ESM raw URL page are playing as expected`, async ({ page, pomPages }) => { 10 | await page.goto(ESM_URL); 11 | await testRawUrlPageVideoIsPlaying(page, pomPages, link); 12 | }); 13 | -------------------------------------------------------------------------------- /test/e2e/specs/ESM/esmRecommendationsPage.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 3 | import { testRecommendationsPageVideoIsPlaying } from '../commonSpecs/recommendationsPageVideoPlaying'; 4 | import { getEsmLinkByName } from '../../testData/esmPageLinksData'; 5 | import { ESM_URL } from '../../testData/esmUrl'; 6 | 7 | const link = getEsmLinkByName(ExampleLinkName.Recommendations); 8 | 9 | vpTest(`Test if video on ESM recommendations page is playing as expected`, async ({ page, pomPages }) => { 10 | await page.goto(ESM_URL); 11 | await testRecommendationsPageVideoIsPlaying(page, pomPages, link); 12 | }); 13 | -------------------------------------------------------------------------------- /test/e2e/specs/ESM/esmSeekThumbnailsPage.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 3 | import { testSeekThumbnailsPageVideoIsPlaying } from '../commonSpecs/seekThumbnailsPageVideoPlaying'; 4 | import { getEsmLinkByName } from '../../testData/esmPageLinksData'; 5 | import { ESM_URL } from '../../testData/esmUrl'; 6 | 7 | const link = getEsmLinkByName(ExampleLinkName.SeekThumbnails); 8 | 9 | vpTest(`Test if video on ESM seek thumbnails page is playing as expected`, async ({ page, pomPages }) => { 10 | await page.goto(ESM_URL); 11 | await testSeekThumbnailsPageVideoIsPlaying(page, pomPages, link); 12 | }); 13 | -------------------------------------------------------------------------------- /test/e2e/specs/ESM/esmShoppableVideosPage.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 3 | import { testShoppableVideosPageVideoIsPlaying } from '../commonSpecs/shoppableVideosPageVideoPlaying'; 4 | import { getEsmLinkByName } from '../../testData/esmPageLinksData'; 5 | import { ESM_URL } from '../../testData/esmUrl'; 6 | 7 | const link = getEsmLinkByName(ExampleLinkName.ShoppableVideos); 8 | 9 | vpTest(`Test if video on ESM shoppable videos page is playing as expected`, async ({ page, pomPages }) => { 10 | await page.goto(ESM_URL); 11 | await testShoppableVideosPageVideoIsPlaying(page, pomPages, link); 12 | }); 13 | -------------------------------------------------------------------------------- /test/e2e/specs/ESM/esmSubtitlesAndCaptionsPage.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 3 | import { testSubtitlesAndCaptionsPageVideoIsPlaying } from '../commonSpecs/subtitlesAndCaptionsPgaeVideoPlaying'; 4 | import { getEsmLinkByName } from '../../testData/esmPageLinksData'; 5 | import { ESM_URL } from '../../testData/esmUrl'; 6 | 7 | const link = getEsmLinkByName(ExampleLinkName.SubtitlesAndCaptions); 8 | 9 | vpTest(`Test if 5 videos on ESM subtitles and captions page are playing as expected`, async ({ page, pomPages }) => { 10 | await page.goto(ESM_URL); 11 | await testSubtitlesAndCaptionsPageVideoIsPlaying(page, pomPages, link); 12 | }); 13 | -------------------------------------------------------------------------------- /test/e2e/specs/ESM/esmVastAndVpaidPage.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 3 | import { testVastAndVpaidPageVideoIsPlaying } from '../commonSpecs/vastAndVpaidPage'; 4 | import { getEsmLinkByName } from '../../testData/esmPageLinksData'; 5 | import { ESM_URL } from '../../testData/esmUrl'; 6 | 7 | const link = getEsmLinkByName(ExampleLinkName.VASTAndVPAIDSupport); 8 | 9 | vpTest(`Test if 2 videos on ESM vast and vpaid page are playing as expected`, async ({ page, pomPages }) => { 10 | await page.goto(ESM_URL); 11 | await testVastAndVpaidPageVideoIsPlaying(page, pomPages, link); 12 | }); 13 | -------------------------------------------------------------------------------- /test/e2e/specs/ESM/esmVideoTransformationsPage.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 3 | import { testVideoTransformationsPageVideoIsPlaying } from '../commonSpecs/videoTransformationsPage'; 4 | import { getEsmLinkByName } from '../../testData/esmPageLinksData'; 5 | import { ESM_URL } from '../../testData/esmUrl'; 6 | 7 | const link = getEsmLinkByName(ExampleLinkName.VideoTransformations); 8 | 9 | vpTest(`Test if 3 videos on ESM video transformations page are playing as expected`, async ({ page, pomPages }) => { 10 | await page.goto(ESM_URL); 11 | await testVideoTransformationsPageVideoIsPlaying(page, pomPages, link); 12 | }); 13 | -------------------------------------------------------------------------------- /test/e2e/specs/ESM/esmVr360Page.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 3 | import { testVr360PageVideoIsPlaying } from '../commonSpecs/vr360VideosPageVideoPlaying'; 4 | import { getEsmLinkByName } from '../../testData/esmPageLinksData'; 5 | import { ESM_URL } from '../../testData/esmUrl'; 6 | 7 | const link = getEsmLinkByName(ExampleLinkName.VR360Videos); 8 | 9 | vpTest(`Test if video on ESM VR 360 videos page is playing as expected`, async ({ page, pomPages }) => { 10 | await page.goto(ESM_URL); 11 | await testVr360PageVideoIsPlaying(page, pomPages, link); 12 | }); 13 | -------------------------------------------------------------------------------- /test/e2e/specs/NonESM/analyticsPage.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { test } from '@playwright/test'; 3 | import { waitForPageToLoadWithTimeout } from '../../src/helpers/waitForPageToLoadWithTimeout'; 4 | import { getLinkByName } from '../../testData/pageLinksData'; 5 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 6 | 7 | // Link to Analytics page 8 | const link = getLinkByName(ExampleLinkName.Analytics); 9 | /** 10 | * Testing if video on analytics page is playing by checking that is pause return false. 11 | */ 12 | vpTest(`Test if video on analytics page is playing as expected`, async ({ page, pomPages }) => { 13 | await test.step('Navigate to analytics page by clicking on link', async () => { 14 | await pomPages.mainPage.clickLinkByName(link.name); 15 | await waitForPageToLoadWithTimeout(page, 5000); 16 | }); 17 | await test.step('Validating that the video is playing', async () => { 18 | await pomPages.analyticsPage.analyticsVideoComponent.validateVideoIsPlaying(true); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /test/e2e/specs/NonESM/apiAndEventsPage.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { getLinkByName } from '../../testData/pageLinksData'; 3 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 4 | import { testApiAndEventsPageVideoIsPlaying } from '../commonSpecs/apiAndEventsPageVideoPlaying'; 5 | 6 | // Link to API and Events page 7 | const link = getLinkByName(ExampleLinkName.APIAndEvents); 8 | 9 | vpTest(`Test if video on API and Events page is playing as expected`, async ({ page, pomPages }) => { 10 | await testApiAndEventsPageVideoIsPlaying(page, pomPages, link); 11 | }); 12 | -------------------------------------------------------------------------------- /test/e2e/specs/NonESM/audioPlayerPage.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { getLinkByName } from '../../testData/pageLinksData'; 3 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 4 | import { testAudioPlayerPageVideosArePlaying } from '../commonSpecs/audioPlayerPageVideoPlaying'; 5 | 6 | // Link to audio player page 7 | const link = getLinkByName(ExampleLinkName.AudioPlayer); 8 | /** 9 | * Testing if videos on audio player page are playing by checking that is pause return false. 10 | */ 11 | vpTest(`Test if 2 videos on audio player page are playing as expected`, async ({ page, pomPages }) => { 12 | await testAudioPlayerPageVideosArePlaying(page, pomPages, link); 13 | }); 14 | -------------------------------------------------------------------------------- /test/e2e/specs/NonESM/autoplayOnScrollPage.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { getLinkByName } from '../../testData/pageLinksData'; 3 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 4 | import { testAutoplayOnScrollPageVideoIsPlaying } from '../commonSpecs/autoplayOnScrollVideoPlaying'; 5 | 6 | // Link to autoplay on scroll page 7 | const link = getLinkByName(ExampleLinkName.AutoplayOnScroll); 8 | 9 | vpTest(`Test if video on autoplay on scroll page is playing as expected`, async ({ page, pomPages }) => { 10 | await testAutoplayOnScrollPageVideoIsPlaying(page, pomPages, link); 11 | }); 12 | -------------------------------------------------------------------------------- /test/e2e/specs/NonESM/chaptersPage.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { getLinkByName } from '../../testData/pageLinksData'; 3 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 4 | import { testChaptersPageVideosArePlaying } from '../commonSpecs/chaptersPage'; 5 | 6 | const link = getLinkByName(ExampleLinkName.Chapters); 7 | 8 | vpTest(`Test if 3 videos on chapters page are playing as expected`, async ({ page, pomPages }) => { 9 | await testChaptersPageVideosArePlaying(page, pomPages, link); 10 | }); 11 | -------------------------------------------------------------------------------- /test/e2e/specs/NonESM/cldAnalyticsPage.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { getLinkByName } from '../../testData/pageLinksData'; 3 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 4 | import { testCldAnalyticsPageVideoIsPlaying } from '../commonSpecs/cldAnalyticsPageVideoPlaying'; 5 | 6 | const link = getLinkByName(ExampleLinkName.CloudinaryAnalytics); 7 | 8 | vpTest(`Test if 4 videos on Cloudinary analytics page are playing as expected`, async ({ page, pomPages }) => { 9 | await testCldAnalyticsPageVideoIsPlaying(page, pomPages, link); 10 | }); 11 | -------------------------------------------------------------------------------- /test/e2e/specs/NonESM/codecsAndFormats.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { getLinkByName } from '../../testData/pageLinksData'; 3 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 4 | import { testCodecsAndFormatsPageVideoIsPlaying } from '../commonSpecs/codecsAndFormatsVideoPlaying'; 5 | 6 | const link = getLinkByName(ExampleLinkName.CodecsAndFormats); 7 | 8 | vpTest(`Test if 3 videos on codecs and formats page are playing as expected`, async ({ page, pomPages }) => { 9 | await testCodecsAndFormatsPageVideoIsPlaying(page, pomPages, link); 10 | }); 11 | -------------------------------------------------------------------------------- /test/e2e/specs/NonESM/colorsApiPage.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { getLinkByName } from '../../testData/pageLinksData'; 3 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 4 | import { testColorsApiPageVideoIsPlaying } from '../commonSpecs/colorsApiPageVideoPlaying'; 5 | 6 | const link = getLinkByName(ExampleLinkName.ColorsAPI); 7 | 8 | vpTest(`Test if 3 videos on colors API page are playing as expected`, async ({ page, pomPages }) => { 9 | await testColorsApiPageVideoIsPlaying(page, pomPages, link); 10 | }); 11 | -------------------------------------------------------------------------------- /test/e2e/specs/NonESM/componentsPage.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { getLinkByName } from '../../testData/pageLinksData'; 3 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 4 | import { testComponentsPageVideoIsPlaying } from '../commonSpecs/componentsPageVideoPlaying'; 5 | 6 | const link = getLinkByName(ExampleLinkName.Components); 7 | 8 | vpTest(`Test if video on components page is playing as expected`, async ({ page, pomPages }) => { 9 | await testComponentsPageVideoIsPlaying(page, pomPages, link); 10 | }); 11 | -------------------------------------------------------------------------------- /test/e2e/specs/NonESM/displayConfigurationsPage.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { getLinkByName } from '../../testData/pageLinksData'; 3 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 4 | import { testDisplayConfigurationPageVideoIsPlaying } from '../commonSpecs/displayConfigurationPageVideoPlaying'; 5 | 6 | const link = getLinkByName(ExampleLinkName.DisplayConfigurations); 7 | 8 | vpTest(`Test if video on display configurations page is playing as expected`, async ({ page, pomPages }) => { 9 | await testDisplayConfigurationPageVideoIsPlaying(page, pomPages, link); 10 | }); 11 | -------------------------------------------------------------------------------- /test/e2e/specs/NonESM/floatingPlayerPgae.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { getLinkByName } from '../../testData/pageLinksData'; 3 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 4 | import { testFloatingPlayerPageVideoIsPlaying } from '../commonSpecs/floatingPlayerPageVideoPlaying'; 5 | 6 | const link = getLinkByName(ExampleLinkName.FloatingPlayer); 7 | 8 | vpTest(`Test if video on floating player page is playing as expected`, async ({ page, pomPages }) => { 9 | await testFloatingPlayerPageVideoIsPlaying(page, pomPages, link); 10 | }); 11 | -------------------------------------------------------------------------------- /test/e2e/specs/NonESM/fluidLayoutsPage.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { getLinkByName } from '../../testData/pageLinksData'; 3 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 4 | import { testFluidLayoutsPageVideoIsPlaying } from '../commonSpecs/fluidLayoutsPageVideoPlaying'; 5 | 6 | const link = getLinkByName(ExampleLinkName.FluidLayouts); 7 | 8 | vpTest(`Test if video on fluid layouts page is playing as expected`, async ({ page, pomPages }) => { 9 | await testFluidLayoutsPageVideoIsPlaying(page, pomPages, link); 10 | }); 11 | -------------------------------------------------------------------------------- /test/e2e/specs/NonESM/forceHlsSubtitlesPage.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { getLinkByName } from '../../testData/pageLinksData'; 3 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 4 | import { testForceHlsSubtitlesPageVideoIsPlaying } from '../commonSpecs/forceHlsSubtitlesPageVideoPlaying'; 5 | 6 | const link = getLinkByName(ExampleLinkName.ForceHLSSubtitles); 7 | 8 | vpTest(`Test if video on force HLS subtitles page is playing as expected`, async ({ page, pomPages }) => { 9 | await testForceHlsSubtitlesPageVideoIsPlaying(page, pomPages, link); 10 | }); 11 | -------------------------------------------------------------------------------- /test/e2e/specs/NonESM/highlightsGraphPageVideoIsPlaying.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { test } from '@playwright/test'; 3 | import { waitForPageToLoadWithTimeout } from '../../src/helpers/waitForPageToLoadWithTimeout'; 4 | import { getLinkByName } from '../../testData/pageLinksData'; 5 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 6 | 7 | // Link to AI Highlights Graph page 8 | const link = getLinkByName(ExampleLinkName.AIHighlightsGraph); 9 | /** 10 | * Testing if video on highlights graph page is playing by checking that is pause return false. 11 | */ 12 | vpTest(`Test if video on highlights graph page is playing as expected`, async ({ page, pomPages }) => { 13 | await test.step('Navigate to highlights graph page by clicking on link', async () => { 14 | await pomPages.mainPage.clickLinkByName(link.name); 15 | await waitForPageToLoadWithTimeout(page, 5000); 16 | }); 17 | await test.step('Validating that the video is playing', async () => { 18 | await pomPages.highlightGraphPage.videoHighlightsGraphPage.validateVideoIsPlaying(true); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /test/e2e/specs/NonESM/mainPageVideoIsPlaying.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { testMainPageVideoIsPlaying } from '../commonSpecs/mainPageVideoPlaying'; 3 | 4 | /** 5 | * Testing if video on main page is playing by checking that is pause return false. 6 | * The video in the main page is not configured as autoplay so first need to click on play button. 7 | */ 8 | vpTest(`Test if video on main page can play as expected`, async ({ page, pomPages }) => { 9 | await testMainPageVideoIsPlaying(page, pomPages.mainPage.videoMainPage); 10 | }); 11 | -------------------------------------------------------------------------------- /test/e2e/specs/NonESM/multiplePlayersPage.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { getLinkByName } from '../../testData/pageLinksData'; 3 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 4 | import { testMultiplePlayersPageVideoIsPlaying } from '../commonSpecs/multiplePlayersPageVideoPlaying'; 5 | 6 | const link = getLinkByName(ExampleLinkName.MultiplePlayers); 7 | 8 | vpTest(`Test if 3 videos on multiple players page are playing as expected`, async ({ page, pomPages }) => { 9 | await testMultiplePlayersPageVideoIsPlaying(page, pomPages, link); 10 | }); 11 | -------------------------------------------------------------------------------- /test/e2e/specs/NonESM/playlistByTagPage.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { getLinkByName } from '../../testData/pageLinksData'; 3 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 4 | import { testPlaylistByTagPageVideoIsPlaying } from '../commonSpecs/playlistByTagPageVideoPlaying'; 5 | 6 | const link = getLinkByName(ExampleLinkName.PlaylistByTag); 7 | 8 | vpTest(`Test if video on playlist by tag page is playing as expected`, async ({ page, pomPages }) => { 9 | await testPlaylistByTagPageVideoIsPlaying(page, pomPages, link); 10 | }); 11 | -------------------------------------------------------------------------------- /test/e2e/specs/NonESM/playlistPage.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { getLinkByName } from '../../testData/pageLinksData'; 3 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 4 | import { testPlaylistPageVideoIsPlaying } from '../commonSpecs/playlistPageVideoPlaying'; 5 | 6 | const link = getLinkByName(ExampleLinkName.Playlist); 7 | 8 | vpTest(`Test if 2 videos on playlist page are playing as expected`, async ({ page, pomPages }) => { 9 | await testPlaylistPageVideoIsPlaying(page, pomPages, link); 10 | }); 11 | -------------------------------------------------------------------------------- /test/e2e/specs/NonESM/posterOptionsPage.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { getLinkByName } from '../../testData/pageLinksData'; 3 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 4 | import { testPosterOptionsPageVideoIsPlaying } from '../commonSpecs/posterOptionsPage'; 5 | 6 | const link = getLinkByName(ExampleLinkName.PosterOptions); 7 | 8 | vpTest(`Test if 4 videos on poster options page are playing as expected`, async ({ page, pomPages }) => { 9 | await testPosterOptionsPageVideoIsPlaying(page, pomPages, link); 10 | }); 11 | -------------------------------------------------------------------------------- /test/e2e/specs/NonESM/profilesPage.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { getLinkByName } from '../../testData/pageLinksData'; 3 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 4 | import { testProfilesPageVideoIsPlaying } from '../commonSpecs/profilesPageVideoPlaying'; 5 | 6 | const link = getLinkByName(ExampleLinkName.Profiles); 7 | 8 | vpTest(`Test if 3 videos on profiles page are playing as expected`, async ({ page, pomPages }) => { 9 | await testProfilesPageVideoIsPlaying(page, pomPages, link); 10 | }); 11 | -------------------------------------------------------------------------------- /test/e2e/specs/NonESM/rawUrlPage.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { getLinkByName } from '../../testData/pageLinksData'; 3 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 4 | import { testRawUrlPageVideoIsPlaying } from '../commonSpecs/rawUrlPageVideoPlaying'; 5 | 6 | const link = getLinkByName(ExampleLinkName.RawURL); 7 | 8 | vpTest(`Test if 2 videos on raw URL page are playing as expected`, async ({ page, pomPages }) => { 9 | await testRawUrlPageVideoIsPlaying(page, pomPages, link); 10 | }); 11 | -------------------------------------------------------------------------------- /test/e2e/specs/NonESM/recommendationsPage.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { getLinkByName } from '../../testData/pageLinksData'; 3 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 4 | import { testRecommendationsPageVideoIsPlaying } from '../commonSpecs/recommendationsPageVideoPlaying'; 5 | 6 | const link = getLinkByName(ExampleLinkName.Recommendations); 7 | 8 | vpTest(`Test if video on recommendations page is playing as expected`, async ({ page, pomPages }) => { 9 | await testRecommendationsPageVideoIsPlaying(page, pomPages, link); 10 | }); 11 | -------------------------------------------------------------------------------- /test/e2e/specs/NonESM/seekThumbnailsPage.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { getLinkByName } from '../../testData/pageLinksData'; 3 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 4 | import { testSeekThumbnailsPageVideoIsPlaying } from '../commonSpecs/seekThumbnailsPageVideoPlaying'; 5 | 6 | const link = getLinkByName(ExampleLinkName.SeekThumbnails); 7 | 8 | vpTest(`Test if video on seek thumbnails page is playing as expected`, async ({ page, pomPages }) => { 9 | await testSeekThumbnailsPageVideoIsPlaying(page, pomPages, link); 10 | }); 11 | -------------------------------------------------------------------------------- /test/e2e/specs/NonESM/shoppableVideosPage.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { getLinkByName } from '../../testData/pageLinksData'; 3 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 4 | import { testShoppableVideosPageVideoIsPlaying } from '../commonSpecs/shoppableVideosPageVideoPlaying'; 5 | 6 | const link = getLinkByName(ExampleLinkName.ShoppableVideos); 7 | 8 | vpTest(`Test if video on shoppable videos page is playing as expected`, async ({ page, pomPages }) => { 9 | await testShoppableVideosPageVideoIsPlaying(page, pomPages, link); 10 | }); 11 | -------------------------------------------------------------------------------- /test/e2e/specs/NonESM/subtitlesAndCaptionsPage.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { getLinkByName } from '../../testData/pageLinksData'; 3 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 4 | import { testSubtitlesAndCaptionsPageVideoIsPlaying } from '../commonSpecs/subtitlesAndCaptionsPgaeVideoPlaying'; 5 | 6 | const link = getLinkByName(ExampleLinkName.SubtitlesAndCaptions); 7 | 8 | vpTest(`Test if 5 videos on subtitles and captions page are playing as expected`, async ({ page, pomPages }) => { 9 | await testSubtitlesAndCaptionsPageVideoIsPlaying(page, pomPages, link); 10 | }); 11 | -------------------------------------------------------------------------------- /test/e2e/specs/NonESM/vastAndVpaidPage.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { getLinkByName } from '../../testData/pageLinksData'; 3 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 4 | import { testVastAndVpaidPageVideoIsPlaying } from '../commonSpecs/vastAndVpaidPage'; 5 | 6 | const link = getLinkByName(ExampleLinkName.VASTAndVPAIDSupport); 7 | 8 | vpTest(`Test if 2 videos on vast and vpaid page are playing as expected`, async ({ page, pomPages }) => { 9 | await testVastAndVpaidPageVideoIsPlaying(page, pomPages, link); 10 | }); 11 | -------------------------------------------------------------------------------- /test/e2e/specs/NonESM/videoTransformationsPage.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { getLinkByName } from '../../testData/pageLinksData'; 3 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 4 | import { testVideoTransformationsPageVideoIsPlaying } from '../commonSpecs/videoTransformationsPage'; 5 | 6 | const link = getLinkByName(ExampleLinkName.VideoTransformations); 7 | 8 | vpTest(`Test if 3 videos on video transformations page are playing as expected`, async ({ page, pomPages }) => { 9 | await testVideoTransformationsPageVideoIsPlaying(page, pomPages, link); 10 | }); 11 | -------------------------------------------------------------------------------- /test/e2e/specs/NonESM/vr360VideosPage.spec.ts: -------------------------------------------------------------------------------- 1 | import { vpTest } from '../../fixtures/vpTest'; 2 | import { getLinkByName } from '../../testData/pageLinksData'; 3 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 4 | import { testVr360PageVideoIsPlaying } from '../commonSpecs/vr360VideosPageVideoPlaying'; 5 | 6 | const link = getLinkByName(ExampleLinkName.VR360Videos); 7 | 8 | vpTest(`Test if video on VR 360 videos page is playing as expected`, async ({ page, pomPages }) => { 9 | await testVr360PageVideoIsPlaying(page, pomPages, link); 10 | }); 11 | -------------------------------------------------------------------------------- /test/e2e/specs/Setup/global.setup.ts: -------------------------------------------------------------------------------- 1 | import { ExampleLinkType } from '../../types/exampleLinkType'; 2 | import { expect, Page } from '@playwright/test'; 3 | import { ExampleLinkName } from '../../testData/ExampleLinkNames'; 4 | import { ESM_URL } from '../../testData/esmUrl'; 5 | 6 | async function globalSetup(page: Page) { 7 | if (ESM_URL) { 8 | const link: ExampleLinkType = { 9 | name: ExampleLinkName.AdaptiveStreaming, 10 | endpoint: 'adaptive-streaming', 11 | }; 12 | await waitForDeployPreviewUrl(link, page); 13 | } 14 | } 15 | 16 | /** 17 | * Waits for a deploy preview URL to become available by making repeated requests and check that link is visible. 18 | */ 19 | async function waitForDeployPreviewUrl(link: ExampleLinkType, page: Page): Promise { 20 | await expect(async () => { 21 | await page.goto(ESM_URL); 22 | const linkLocator = page.getByRole('link', { name: link.name, exact: true }); 23 | await expect(linkLocator).toBeVisible({ timeout: 10000 }); 24 | }).toPass({ intervals: [1_000], timeout: 120000 }); 25 | } 26 | export default globalSetup; 27 | -------------------------------------------------------------------------------- /test/e2e/specs/commonSpecs/apiAndEventsPageVideoPlaying.ts: -------------------------------------------------------------------------------- 1 | import { Page, test } from '@playwright/test'; 2 | import { waitForPageToLoadWithTimeout } from '../../src/helpers/waitForPageToLoadWithTimeout'; 3 | import PageManager from '../../src/pom/PageManager'; 4 | import { ExampleLinkType } from '../../types/exampleLinkType'; 5 | 6 | export async function testApiAndEventsPageVideoIsPlaying(page: Page, pomPages: PageManager, link: ExampleLinkType) { 7 | await test.step('Navigate to api and events page by clicking on link', async () => { 8 | await pomPages.mainPage.clickLinkByName(link.name); 9 | await waitForPageToLoadWithTimeout(page, 5000); 10 | }); 11 | await test.step('Validating that the video is playing', async () => { 12 | await pomPages.apiAndEventsPage.apiAndEventsVideoComponent.validateVideoIsPlaying(true); 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /test/e2e/specs/commonSpecs/audioPlayerPageVideoPlaying.ts: -------------------------------------------------------------------------------- 1 | import { Page, test } from '@playwright/test'; 2 | import { waitForPageToLoadWithTimeout } from '../../src/helpers/waitForPageToLoadWithTimeout'; 3 | import PageManager from '../../src/pom/PageManager'; 4 | import { ExampleLinkType } from '../../types/exampleLinkType'; 5 | 6 | export async function testAudioPlayerPageVideosArePlaying(page: Page, pomPages: PageManager, link: ExampleLinkType) { 7 | await test.step('Navigate to audio player page by clicking on link', async () => { 8 | await pomPages.mainPage.clickLinkByName(link.name); 9 | await waitForPageToLoadWithTimeout(page, 5000); 10 | }); 11 | await test.step('Click on play button of video player to play video', async () => { 12 | return pomPages.audioPlayerPage.audioPlayerVideoComponent.clickPlay(); 13 | }); 14 | await test.step('Validating that the first video player is playing', async () => { 15 | await pomPages.audioPlayerPage.audioPlayerVideoComponent.validateVideoIsPlaying(true); 16 | }); 17 | await test.step('Click on play button of audio player with transformation to play video', async () => { 18 | return pomPages.audioPlayerPage.audioPlayerWithTransformationVideoComponent.clickPlay(); 19 | }); 20 | await test.step('Validating that the audio player with transformation is playing', async () => { 21 | await pomPages.audioPlayerPage.audioPlayerWithTransformationVideoComponent.validateVideoIsPlaying(true); 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /test/e2e/specs/commonSpecs/autoplayOnScrollVideoPlaying.ts: -------------------------------------------------------------------------------- 1 | import { Page, test } from '@playwright/test'; 2 | import { waitForPageToLoadWithTimeout } from '../../src/helpers/waitForPageToLoadWithTimeout'; 3 | import PageManager from '../../src/pom/PageManager'; 4 | import { ExampleLinkType } from '../../types/exampleLinkType'; 5 | 6 | export async function testAutoplayOnScrollPageVideoIsPlaying(page: Page, pomPages: PageManager, link: ExampleLinkType) { 7 | await test.step('Navigate to autoplay on scroll page by clicking on link', async () => { 8 | await pomPages.mainPage.clickLinkByName(link.name); 9 | await waitForPageToLoadWithTimeout(page, 5000); 10 | }); 11 | await test.step('Validating that the video is not playing before scrolling', async () => { 12 | await pomPages.autoplayOnScrollPage.autoplayOnScrollVideoComponent.validateVideoIsPlaying(false); 13 | }); 14 | await test.step('Scroll until the video element is visible', async () => { 15 | await pomPages.autoplayOnScrollPage.autoplayOnScrollVideoComponent.locator.scrollIntoViewIfNeeded(); 16 | }); 17 | await test.step('Validating that the video is auto playing after scrolling', async () => { 18 | await pomPages.autoplayOnScrollPage.autoplayOnScrollVideoComponent.validateVideoIsPlaying(true); 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /test/e2e/specs/commonSpecs/chaptersPage.ts: -------------------------------------------------------------------------------- 1 | import { Page, test } from '@playwright/test'; 2 | import { waitForPageToLoadWithTimeout } from '../../src/helpers/waitForPageToLoadWithTimeout'; 3 | import PageManager from '../../src/pom/PageManager'; 4 | import { ExampleLinkType } from '../../types/exampleLinkType'; 5 | 6 | export async function testChaptersPageVideosArePlaying(page: Page, pomPages: PageManager, link: ExampleLinkType) { 7 | await test.step('Navigate to chapters page by clicking on link', async () => { 8 | await pomPages.mainPage.clickLinkByName(link.name); 9 | await waitForPageToLoadWithTimeout(page, 5000); 10 | }); 11 | await test.step('Validating that chapters vtt file video is playing', async () => { 12 | await pomPages.chaptersPage.chaptersVttFIleVideoComponent.validateVideoIsPlaying(true); 13 | }); 14 | await test.step('Validating that chapters config object video is playing', async () => { 15 | await pomPages.chaptersPage.chaptersConfigObjectVideoComponent.validateVideoIsPlaying(true); 16 | }); 17 | await test.step('Scroll until chapters auto vtt file video element is visible', async () => { 18 | await pomPages.chaptersPage.chapterAutoVttFileVideoComponent.locator.scrollIntoViewIfNeeded(); 19 | }); 20 | await test.step('Validating that chapters auto vtt file video is playing', async () => { 21 | await pomPages.chaptersPage.chapterAutoVttFileVideoComponent.validateVideoIsPlaying(true); 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /test/e2e/specs/commonSpecs/codecsAndFormatsVideoPlaying.ts: -------------------------------------------------------------------------------- 1 | import { Page, test } from '@playwright/test'; 2 | import { waitForPageToLoadWithTimeout } from '../../src/helpers/waitForPageToLoadWithTimeout'; 3 | import PageManager from '../../src/pom/PageManager'; 4 | import { ExampleLinkType } from '../../types/exampleLinkType'; 5 | 6 | export async function testCodecsAndFormatsPageVideoIsPlaying(page: Page, pomPages: PageManager, link: ExampleLinkType) { 7 | await test.step('Navigate to codecs and formats page by clicking on link', async () => { 8 | await pomPages.mainPage.clickLinkByName(link.name); 9 | await waitForPageToLoadWithTimeout(page, 5000); 10 | }); 11 | await test.step('Validating that f_auto video is playing', async () => { 12 | await pomPages.codecsAndFormatsPage.codecsAndFormatsFAutoVideoComponent.validateVideoIsPlaying(true); 13 | }); 14 | await test.step('Validating that AV1 video is playing', async () => { 15 | await pomPages.codecsAndFormatsPage.codecsAndFormatsAv1VideoComponent.validateVideoIsPlaying(true); 16 | }); 17 | await test.step('Scroll until VP9 video element is visible', async () => { 18 | await pomPages.codecsAndFormatsPage.codecsAndFormatsVp9VideoComponent.locator.scrollIntoViewIfNeeded(); 19 | }); 20 | await test.step('Validating that VP9 video is playing', async () => { 21 | await pomPages.codecsAndFormatsPage.codecsAndFormatsVp9VideoComponent.validateVideoIsPlaying(true); 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /test/e2e/specs/commonSpecs/colorsApiPageVideoPlaying.ts: -------------------------------------------------------------------------------- 1 | import { Page, test } from '@playwright/test'; 2 | import { waitForPageToLoadWithTimeout } from '../../src/helpers/waitForPageToLoadWithTimeout'; 3 | import PageManager from '../../src/pom/PageManager'; 4 | import { ExampleLinkType } from '../../types/exampleLinkType'; 5 | 6 | export async function testColorsApiPageVideoIsPlaying(page: Page, pomPages: PageManager, link: ExampleLinkType) { 7 | await test.step('Navigate to colors API page by clicking on link', async () => { 8 | await pomPages.mainPage.clickLinkByName(link.name); 9 | await waitForPageToLoadWithTimeout(page, 5000); 10 | }); 11 | await test.step('Validating that modified color video is playing', async () => { 12 | await pomPages.colorsApiPage.colorsApiColorSkinVideoComponent.validateVideoIsPlaying(true); 13 | }); 14 | await test.step('Validating that dark skin video video is playing', async () => { 15 | await pomPages.colorsApiPage.colorsApiDarkSkinVideoComponent.validateVideoIsPlaying(true); 16 | }); 17 | await test.step('Scroll until light skin video element is visible', async () => { 18 | await pomPages.colorsApiPage.colorsApiLightSkinVideoComponent.locator.scrollIntoViewIfNeeded(); 19 | }); 20 | await test.step('Validating that light skin video is playing', async () => { 21 | await pomPages.colorsApiPage.colorsApiLightSkinVideoComponent.validateVideoIsPlaying(true); 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /test/e2e/specs/commonSpecs/componentsPageVideoPlaying.ts: -------------------------------------------------------------------------------- 1 | import { Page, test } from '@playwright/test'; 2 | import { waitForPageToLoadWithTimeout } from '../../src/helpers/waitForPageToLoadWithTimeout'; 3 | import PageManager from '../../src/pom/PageManager'; 4 | import { ExampleLinkType } from '../../types/exampleLinkType'; 5 | 6 | export async function testComponentsPageVideoIsPlaying(page: Page, pomPages: PageManager, link: ExampleLinkType) { 7 | await test.step('Navigate to components page by clicking on link', async () => { 8 | await pomPages.mainPage.clickLinkByName(link.name); 9 | await waitForPageToLoadWithTimeout(page, 5000); 10 | }); 11 | await test.step('Validating that components video is playing', async () => { 12 | await pomPages.componentsPage.componentsVideoComponent.validateVideoIsPlaying(true); 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /test/e2e/specs/commonSpecs/displayConfigurationPageVideoPlaying.ts: -------------------------------------------------------------------------------- 1 | import { Page, test } from '@playwright/test'; 2 | import { waitForPageToLoadWithTimeout } from '../../src/helpers/waitForPageToLoadWithTimeout'; 3 | import PageManager from '../../src/pom/PageManager'; 4 | import { ExampleLinkType } from '../../types/exampleLinkType'; 5 | 6 | export async function testDisplayConfigurationPageVideoIsPlaying(page: Page, pomPages: PageManager, link: ExampleLinkType) { 7 | await test.step('Navigate to display configurations page by clicking on link', async () => { 8 | await pomPages.mainPage.clickLinkByName(link.name); 9 | await waitForPageToLoadWithTimeout(page, 5000); 10 | }); 11 | await test.step('Validating that display configuration video is playing', async () => { 12 | await pomPages.displayConfigurationsPage.displayConfigurationsPageVideoComponent.validateVideoIsPlaying(true); 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /test/e2e/specs/commonSpecs/floatingPlayerPageVideoPlaying.ts: -------------------------------------------------------------------------------- 1 | import { Page, test } from '@playwright/test'; 2 | import { waitForPageToLoadWithTimeout } from '../../src/helpers/waitForPageToLoadWithTimeout'; 3 | import PageManager from '../../src/pom/PageManager'; 4 | import { ExampleLinkType } from '../../types/exampleLinkType'; 5 | 6 | export async function testFloatingPlayerPageVideoIsPlaying(page: Page, pomPages: PageManager, link: ExampleLinkType) { 7 | await test.step('Navigate to floating player page by clicking on link', async () => { 8 | await pomPages.mainPage.clickLinkByName(link.name); 9 | await waitForPageToLoadWithTimeout(page, 5000); 10 | }); 11 | await test.step('Validating that floating player video is playing', async () => { 12 | await pomPages.floatingPlayerPage.floatingPlayerVideoComponent.validateVideoIsPlaying(true); 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /test/e2e/specs/commonSpecs/fluidLayoutsPageVideoPlaying.ts: -------------------------------------------------------------------------------- 1 | import { Page, test } from '@playwright/test'; 2 | import { waitForPageToLoadWithTimeout } from '../../src/helpers/waitForPageToLoadWithTimeout'; 3 | import PageManager from '../../src/pom/PageManager'; 4 | import { ExampleLinkType } from '../../types/exampleLinkType'; 5 | 6 | export async function testFluidLayoutsPageVideoIsPlaying(page: Page, pomPages: PageManager, link: ExampleLinkType) { 7 | await test.step('Navigate to fluid layouts page by clicking on link', async () => { 8 | await pomPages.mainPage.clickLinkByName(link.name); 9 | await waitForPageToLoadWithTimeout(page, 5000); 10 | }); 11 | await test.step('Validating that fluid layouts video is playing', async () => { 12 | await pomPages.fluidLayoutsPage.fluidLayoutsVideoComponent.validateVideoIsPlaying(true); 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /test/e2e/specs/commonSpecs/forceHlsSubtitlesPageVideoPlaying.ts: -------------------------------------------------------------------------------- 1 | import { Page, test } from '@playwright/test'; 2 | import { waitForPageToLoadWithTimeout } from '../../src/helpers/waitForPageToLoadWithTimeout'; 3 | import PageManager from '../../src/pom/PageManager'; 4 | import { ExampleLinkType } from '../../types/exampleLinkType'; 5 | 6 | export async function testForceHlsSubtitlesPageVideoIsPlaying(page: Page, pomPages: PageManager, link: ExampleLinkType) { 7 | await test.step('Navigate to force HLS subtitles page by clicking on link', async () => { 8 | await pomPages.mainPage.clickLinkByName(link.name); 9 | await waitForPageToLoadWithTimeout(page, 5000); 10 | }); 11 | await test.step('Validating that force HLS subtitles video is playing', async () => { 12 | await pomPages.forceHlsSubtitlesPage.forceHlsSubtitlesVideoComponent.validateVideoIsPlaying(true); 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /test/e2e/specs/commonSpecs/mainPageVideoPlaying.ts: -------------------------------------------------------------------------------- 1 | import { Page, test } from '@playwright/test'; 2 | import { waitForPageToLoadWithTimeout } from '../../src/helpers/waitForPageToLoadWithTimeout'; 3 | import { VideoComponent } from '../../components/videoComponent'; 4 | 5 | export async function testMainPageVideoIsPlaying(page: Page, videoElement: VideoComponent) { 6 | await test.step('Click on play button to play video', async () => { 7 | await waitForPageToLoadWithTimeout(page, 5000); 8 | return videoElement.clickPlay(); 9 | }); 10 | await test.step('Validating that the video is playing', async () => { 11 | await videoElement.validateVideoIsPlaying(true); 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /test/e2e/specs/commonSpecs/multiplePlayersPageVideoPlaying.ts: -------------------------------------------------------------------------------- 1 | import { Page, test } from '@playwright/test'; 2 | import { waitForPageToLoadWithTimeout } from '../../src/helpers/waitForPageToLoadWithTimeout'; 3 | import PageManager from '../../src/pom/PageManager'; 4 | import { ExampleLinkType } from '../../types/exampleLinkType'; 5 | 6 | export async function testMultiplePlayersPageVideoIsPlaying(page: Page, pomPages: PageManager, link: ExampleLinkType) { 7 | await test.step('Navigate to multiple players page by clicking on link', async () => { 8 | await pomPages.mainPage.clickLinkByName(link.name); 9 | await waitForPageToLoadWithTimeout(page, 5000); 10 | }); 11 | await test.step('Validating that player 1 video is playing', async () => { 12 | await pomPages.multiplePlayersPage.multiplePlayersPlayer1VideoComponent.validateVideoIsPlaying(true); 13 | }); 14 | await test.step('Validating that player 2 video video is playing', async () => { 15 | await pomPages.multiplePlayersPage.multiplePlayersPlayer2VideoComponent.validateVideoIsPlaying(true); 16 | }); 17 | await test.step('Scroll until player 3 video element is visible', async () => { 18 | await pomPages.colorsApiPage.colorsApiLightSkinVideoComponent.locator.scrollIntoViewIfNeeded(); 19 | }); 20 | await test.step('Validating that player 3 video is playing', async () => { 21 | await pomPages.multiplePlayersPage.multiplePlayersPlayer3VideoComponent.validateVideoIsPlaying(true); 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /test/e2e/specs/commonSpecs/playlistByTagPageVideoPlaying.ts: -------------------------------------------------------------------------------- 1 | import { Page, test } from '@playwright/test'; 2 | import { waitForPageToLoadWithTimeout } from '../../src/helpers/waitForPageToLoadWithTimeout'; 3 | import PageManager from '../../src/pom/PageManager'; 4 | import { ExampleLinkType } from '../../types/exampleLinkType'; 5 | 6 | export async function testPlaylistByTagPageVideoIsPlaying(page: Page, pomPages: PageManager, link: ExampleLinkType) { 7 | await test.step('Navigate to playlist by tag page by clicking on link', async () => { 8 | await pomPages.mainPage.clickLinkByName(link.name); 9 | await waitForPageToLoadWithTimeout(page, 5000); 10 | }); 11 | await test.step('Validating that playlist by tag video is playing', async () => { 12 | await pomPages.playlistByTagPage.playlistByTagVideoComponent.validateVideoIsPlaying(true); 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /test/e2e/specs/commonSpecs/playlistPageVideoPlaying.ts: -------------------------------------------------------------------------------- 1 | import { Page, test } from '@playwright/test'; 2 | import { waitForPageToLoadWithTimeout } from '../../src/helpers/waitForPageToLoadWithTimeout'; 3 | import PageManager from '../../src/pom/PageManager'; 4 | import { ExampleLinkType } from '../../types/exampleLinkType'; 5 | 6 | export async function testPlaylistPageVideoIsPlaying(page: Page, pomPages: PageManager, link: ExampleLinkType) { 7 | await test.step('Navigate to playlist page by clicking on link', async () => { 8 | await pomPages.mainPage.clickLinkByName(link.name); 9 | await waitForPageToLoadWithTimeout(page, 5000); 10 | }); 11 | await test.step('Validating that horizontal playlist video is playing', async () => { 12 | await pomPages.playlistPage.playlistHorizontalVideoComponent.validateVideoIsPlaying(true); 13 | }); 14 | await test.step('Validating that vertical playlist video is playing', async () => { 15 | await pomPages.playlistPage.playlistVerticalVideoComponent.validateVideoIsPlaying(true); 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /test/e2e/specs/commonSpecs/rawUrlPageVideoPlaying.ts: -------------------------------------------------------------------------------- 1 | import { Page, test } from '@playwright/test'; 2 | import { waitForPageToLoadWithTimeout } from '../../src/helpers/waitForPageToLoadWithTimeout'; 3 | import PageManager from '../../src/pom/PageManager'; 4 | import { ExampleLinkType } from '../../types/exampleLinkType'; 5 | 6 | export async function testRawUrlPageVideoIsPlaying(page: Page, pomPages: PageManager, link: ExampleLinkType) { 7 | await test.step('Navigate to raw URL page by clicking on link', async () => { 8 | await pomPages.mainPage.clickLinkByName(link.name); 9 | await waitForPageToLoadWithTimeout(page, 5000); 10 | }); 11 | await test.step('Validating that raw url video is playing', async () => { 12 | await pomPages.rawUrlPage.rawUrlVideoComponent.validateVideoIsPlaying(true); 13 | }); 14 | await test.step('Validating that raw url adaptive video is playing', async () => { 15 | await pomPages.rawUrlPage.rawUrlAdaptiveVideoComponent.validateVideoIsPlaying(true); 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /test/e2e/specs/commonSpecs/recommendationsPageVideoPlaying.ts: -------------------------------------------------------------------------------- 1 | import { Page, test } from '@playwright/test'; 2 | import { waitForPageToLoadWithTimeout } from '../../src/helpers/waitForPageToLoadWithTimeout'; 3 | import PageManager from '../../src/pom/PageManager'; 4 | import { ExampleLinkType } from '../../types/exampleLinkType'; 5 | 6 | export async function testRecommendationsPageVideoIsPlaying(page: Page, pomPages: PageManager, link: ExampleLinkType) { 7 | await test.step('Navigate to recommendations page by clicking on link', async () => { 8 | await pomPages.mainPage.clickLinkByName(link.name); 9 | await waitForPageToLoadWithTimeout(page, 5000); 10 | }); 11 | await test.step('Validating that recommendations video is playing', async () => { 12 | await pomPages.recommendationsPage.recommendationsVideoComponent.validateVideoIsPlaying(true); 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /test/e2e/specs/commonSpecs/seekThumbnailsPageVideoPlaying.ts: -------------------------------------------------------------------------------- 1 | import { Page, test } from '@playwright/test'; 2 | import { waitForPageToLoadWithTimeout } from '../../src/helpers/waitForPageToLoadWithTimeout'; 3 | import PageManager from '../../src/pom/PageManager'; 4 | import { ExampleLinkType } from '../../types/exampleLinkType'; 5 | 6 | export async function testSeekThumbnailsPageVideoIsPlaying(page: Page, pomPages: PageManager, link: ExampleLinkType) { 7 | await test.step('Navigate to seek thumbnails page by clicking on link', async () => { 8 | await pomPages.mainPage.clickLinkByName(link.name); 9 | await waitForPageToLoadWithTimeout(page, 5000); 10 | }); 11 | await test.step('Validating that seek thumbnails video is playing', async () => { 12 | await pomPages.seekThumbnailsPage.seekThumbnailsVideoComponent.validateVideoIsPlaying(true); 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /test/e2e/specs/commonSpecs/shoppableVideosPageVideoPlaying.ts: -------------------------------------------------------------------------------- 1 | import { Page, test } from '@playwright/test'; 2 | import { waitForPageToLoadWithTimeout } from '../../src/helpers/waitForPageToLoadWithTimeout'; 3 | import PageManager from '../../src/pom/PageManager'; 4 | import { ExampleLinkType } from '../../types/exampleLinkType'; 5 | 6 | export async function testShoppableVideosPageVideoIsPlaying(page: Page, pomPages: PageManager, link: ExampleLinkType) { 7 | await test.step('Navigate to shoppable video page by clicking on link', async () => { 8 | await pomPages.mainPage.clickLinkByName(link.name); 9 | await waitForPageToLoadWithTimeout(page, 5000); 10 | }); 11 | await test.step('Click on play button of shoppable videos to play video', async () => { 12 | return pomPages.shoppableVideosPage.shoppableVideosVideoComponent.clickPlay(); 13 | }); 14 | await test.step('Validating that shoppable video is playing', async () => { 15 | await pomPages.shoppableVideosPage.shoppableVideosVideoComponent.validateVideoIsPlaying(true); 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /test/e2e/specs/commonSpecs/vastAndVpaidPage.ts: -------------------------------------------------------------------------------- 1 | import { Page, test } from '@playwright/test'; 2 | import { waitForPageToLoadWithTimeout } from '../../src/helpers/waitForPageToLoadWithTimeout'; 3 | import PageManager from '../../src/pom/PageManager'; 4 | import { ExampleLinkType } from '../../types/exampleLinkType'; 5 | 6 | export async function testVastAndVpaidPageVideoIsPlaying(page: Page, pomPages: PageManager, link: ExampleLinkType) { 7 | await test.step('Navigate to vast and vpaid page by clicking on link', async () => { 8 | await pomPages.mainPage.clickLinkByName(link.name); 9 | await waitForPageToLoadWithTimeout(page, 5000); 10 | }); 11 | await test.step('Click on play button of single video with ads to play video', async () => { 12 | return pomPages.vastAndVpaidPage.singleVideoWithAdsVideoComponent.clickPlay(); 13 | }); 14 | //Sending timeout of 12 seconds to wait until the ad finishes (10 sec) and the video will start 15 | await test.step('Validating that single video with ads is playing', async () => { 16 | await pomPages.vastAndVpaidPage.singleVideoWithAdsVideoComponent.validateVideoIsPlaying(true, 12000); 17 | }); 18 | await test.step('Validating that playlist with ads video is playing', async () => { 19 | await pomPages.vastAndVpaidPage.playlistWithAdsVideoComponent.validateVideoIsPlaying(true, 12000); 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /test/e2e/specs/commonSpecs/videoTransformationsPage.ts: -------------------------------------------------------------------------------- 1 | import { Page, test } from '@playwright/test'; 2 | import { waitForPageToLoadWithTimeout } from '../../src/helpers/waitForPageToLoadWithTimeout'; 3 | import PageManager from '../../src/pom/PageManager'; 4 | import { ExampleLinkType } from '../../types/exampleLinkType'; 5 | 6 | export async function testVideoTransformationsPageVideoIsPlaying(page: Page, pomPages: PageManager, link: ExampleLinkType) { 7 | await test.step('Navigate to video transformations page by clicking on link', async () => { 8 | await pomPages.mainPage.clickLinkByName(link.name); 9 | await waitForPageToLoadWithTimeout(page, 5000); 10 | }); 11 | await test.step('Validating that via source transformation video is playing', async () => { 12 | await pomPages.videoTransformationsPage.viaSourceVideoComponent.validateVideoIsPlaying(true); 13 | }); 14 | await test.step('Validating that via player transformation video is playing', async () => { 15 | await pomPages.videoTransformationsPage.viaPlayerVideoComponent.validateVideoIsPlaying(true); 16 | }); 17 | await test.step('Scroll until data cld transformation video element is visible', async () => { 18 | await pomPages.videoTransformationsPage.viaDataCldTransformationsVideoComponent.locator.scrollIntoViewIfNeeded(); 19 | }); 20 | await test.step('Validating that via data cld transformation video is playing', async () => { 21 | await pomPages.videoTransformationsPage.viaDataCldTransformationsVideoComponent.validateVideoIsPlaying(true); 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /test/e2e/specs/commonSpecs/vr360VideosPageVideoPlaying.ts: -------------------------------------------------------------------------------- 1 | import { Page, test } from '@playwright/test'; 2 | import { waitForPageToLoadWithTimeout } from '../../src/helpers/waitForPageToLoadWithTimeout'; 3 | import PageManager from '../../src/pom/PageManager'; 4 | import { ExampleLinkType } from '../../types/exampleLinkType'; 5 | 6 | export async function testVr360PageVideoIsPlaying(page: Page, pomPages: PageManager, link: ExampleLinkType) { 7 | await test.step('Navigate to VR 360 videos page by clicking on link', async () => { 8 | await pomPages.mainPage.clickLinkByName(link.name); 9 | await waitForPageToLoadWithTimeout(page, 5000); 10 | }); 11 | await test.step('Click on play button of 360 video play video', async () => { 12 | return pomPages.vr360VideosPage.vr360VideoComponent.clickPlay(); 13 | }); 14 | await test.step('Validating that 360 video is playing', async () => { 15 | await pomPages.vr360VideosPage.vr360VideoComponent.validateVideoIsPlaying(true, 6000); 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /test/e2e/src/helpers/validatePageErrors.ts: -------------------------------------------------------------------------------- 1 | import { ConsoleMessage, expect } from '@playwright/test'; 2 | 3 | /** 4 | * Validating that the expected error is part of the console errors 5 | */ 6 | export function validatePageErrors(consoleErrors: ConsoleMessage[], expectedErrorMessages: string[], ignoreErrorMessages: string[]): void { 7 | /** 8 | * Filters console messages to exclude ignored messages 9 | */ 10 | const relevantMessages = consoleErrors.filter((consoleError) => !ignoreErrorMessages.some((ignoreError) => consoleError.text().includes(ignoreError))); 11 | 12 | /** 13 | * Filters expected error messages that are not found in relevant console messages 14 | */ 15 | const missingExpectedErrors = expectedErrorMessages.filter((expectedErrorMessage) => !relevantMessages.some((relevantError) => relevantError.text().includes(expectedErrorMessage))); 16 | 17 | expect(missingExpectedErrors.length, `The following expected console errors were not found: ${JSON.stringify(missingExpectedErrors)}`).toBe(0); 18 | 19 | /** 20 | * Filters relevant console messages that are not part of the expected error messages 21 | */ 22 | const unexpectedErrors = relevantMessages.filter((relevantError) => !expectedErrorMessages.some((expectedErrorMessage) => relevantError.text().includes(expectedErrorMessage))); 23 | 24 | expect(unexpectedErrors.length, `The following unexpected console errors were found: ${JSON.stringify(unexpectedErrors)}`).toBe(0); 25 | } 26 | -------------------------------------------------------------------------------- /test/e2e/src/helpers/waitForPageToLoadWithTimeout.ts: -------------------------------------------------------------------------------- 1 | import { Page } from '@playwright/test'; 2 | 3 | /** 4 | * Wait until there are no network connections for at least 5000 ms or for given timeout to pass. 5 | * Needed for console logs to appear. and in pages that loading video 'waitForLoadState('networkidle')' entering a long loop. 6 | */ 7 | export async function waitForPageToLoadWithTimeout(page: Page, timeout: number): Promise { 8 | return Promise.race([page.waitForLoadState('networkidle'), new Promise((r) => setTimeout(r, timeout))]); 9 | } 10 | -------------------------------------------------------------------------------- /test/e2e/src/pom/BasePage.ts: -------------------------------------------------------------------------------- 1 | import { Page } from '@playwright/test'; 2 | 3 | /** 4 | * Base Page represent an example page 5 | * Such as main page, highlight graph page etc. 6 | */ 7 | export class BasePage { 8 | protected page: Page; 9 | 10 | constructor(page: Page) { 11 | this.page = page; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/e2e/src/pom/analyticsPage.ts: -------------------------------------------------------------------------------- 1 | import { Page } from '@playwright/test'; 2 | import { VideoComponent } from '../../components/videoComponent'; 3 | import { BasePage } from './BasePage'; 4 | const ANALYTICS_PAGE_VIDEO_SELECTOR = '//*[@id="player_html5_api"]'; 5 | 6 | /** 7 | * Video player examples analytics page object 8 | */ 9 | export class AnalyticsPage extends BasePage { 10 | public analyticsVideoComponent: VideoComponent; 11 | 12 | constructor(page: Page) { 13 | super(page); 14 | this.analyticsVideoComponent = new VideoComponent(page, ANALYTICS_PAGE_VIDEO_SELECTOR); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/e2e/src/pom/apiAndEventsPage.ts: -------------------------------------------------------------------------------- 1 | import { Page } from '@playwright/test'; 2 | import { VideoComponent } from '../../components/videoComponent'; 3 | import { BasePage } from './BasePage'; 4 | const API_AND_EVENTS_PAGE_VIDEO_SELECTOR = '//*[@id="player_html5_api"]'; 5 | 6 | /** 7 | * Video player examples api and events page object 8 | */ 9 | export class ApiAndEventsPage extends BasePage { 10 | public apiAndEventsVideoComponent: VideoComponent; 11 | 12 | constructor(page: Page) { 13 | super(page); 14 | this.apiAndEventsVideoComponent = new VideoComponent(page, API_AND_EVENTS_PAGE_VIDEO_SELECTOR); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/e2e/src/pom/audioPlayerPage.ts: -------------------------------------------------------------------------------- 1 | import { Page } from '@playwright/test'; 2 | import { VideoComponent } from '../../components/videoComponent'; 3 | import { BasePage } from './BasePage'; 4 | const AUDIO_PLAYER_VIDEO_SELECTOR = '//*[@id="player_html5_api"]'; 5 | const AUDIO_PLAYER_WITH_TRANSFORMATION_VIDEO_SELECTOR = '//*[@id="player-t_html5_api"]'; 6 | /** 7 | * Video player examples audio player page object 8 | */ 9 | export class AudioPlayerPage extends BasePage { 10 | public audioPlayerVideoComponent: VideoComponent; 11 | public audioPlayerWithTransformationVideoComponent: VideoComponent; 12 | 13 | constructor(page: Page) { 14 | super(page); 15 | this.audioPlayerVideoComponent = new VideoComponent(page, AUDIO_PLAYER_VIDEO_SELECTOR); 16 | this.audioPlayerWithTransformationVideoComponent = new VideoComponent(page, AUDIO_PLAYER_WITH_TRANSFORMATION_VIDEO_SELECTOR); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/e2e/src/pom/autoplayOnScrollPage.ts: -------------------------------------------------------------------------------- 1 | import { Page } from '@playwright/test'; 2 | import { VideoComponent } from '../../components/videoComponent'; 3 | import { BasePage } from './BasePage'; 4 | const AUTOPLAY_ON_SCROLL_PAGE_VIDEO_SELECTOR = '//*[@id="player_html5_api"]'; 5 | 6 | /** 7 | * Video player examples autoplay on scroll page object 8 | */ 9 | export class AutoplayOnScrollPage extends BasePage { 10 | public autoplayOnScrollVideoComponent: VideoComponent; 11 | 12 | constructor(page: Page) { 13 | super(page); 14 | this.autoplayOnScrollVideoComponent = new VideoComponent(page, AUTOPLAY_ON_SCROLL_PAGE_VIDEO_SELECTOR); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/e2e/src/pom/chaptersPage.ts: -------------------------------------------------------------------------------- 1 | import { Page } from '@playwright/test'; 2 | import { VideoComponent } from '../../components/videoComponent'; 3 | import { BasePage } from './BasePage'; 4 | const CHAPTERS_PAGE_VTT_FILE_VIDEO_SELECTOR = '//*[@id="player-vtt_html5_api"]'; 5 | const CHAPTERS_PAGE_CONFIG_OBJECT_VIDEO_SELECTOR = '//*[@id="player-config_html5_api"]'; 6 | const CHAPTERS_PAGE_AUTO_VTT_FILE_VIDEO_SELECTOR = '//*[@id="player-auto-vtt_html5_api"]'; 7 | 8 | /** 9 | * Video player examples chapters page object 10 | */ 11 | export class ChaptersPage extends BasePage { 12 | public chaptersVttFIleVideoComponent: VideoComponent; 13 | public chaptersConfigObjectVideoComponent: VideoComponent; 14 | public chapterAutoVttFileVideoComponent: VideoComponent; 15 | 16 | constructor(page: Page) { 17 | super(page); 18 | this.chaptersVttFIleVideoComponent = new VideoComponent(page, CHAPTERS_PAGE_VTT_FILE_VIDEO_SELECTOR); 19 | this.chaptersConfigObjectVideoComponent = new VideoComponent(page, CHAPTERS_PAGE_CONFIG_OBJECT_VIDEO_SELECTOR); 20 | this.chapterAutoVttFileVideoComponent = new VideoComponent(page, CHAPTERS_PAGE_AUTO_VTT_FILE_VIDEO_SELECTOR); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/e2e/src/pom/cldAnalyticsPage.ts: -------------------------------------------------------------------------------- 1 | import { Page } from '@playwright/test'; 2 | import { VideoComponent } from '../../components/videoComponent'; 3 | import { BasePage } from './BasePage'; 4 | const CLD_ANALYTICS_PAGE_VIDEO_SELECTOR = '//*[@id="player_html5_api"]'; 5 | const CLD_ANALYTICS_PAGE_ADP_VIDEO_SELECTOR = '//*[@id="adpPlayer_html5_api"]'; 6 | const CLD_ANALYTICS_PAGE_CUSTOM_DATA_OBJECT_VIDEO_SELECTOR = '//*[@id="player-custom-data-plain-object_html5_api"]'; 7 | const CLD_ANALYTICS_PAGE_CUSTOM_DATA_FUNCTION_VIDEO_SELECTOR = '//*[@id="player-custom-data-function_html5_api"]'; 8 | 9 | /** 10 | * Video player examples chapters page object 11 | */ 12 | export class CldAnalyticsPage extends BasePage { 13 | public cldAnalyticsVideoComponent: VideoComponent; 14 | public cldAnalyticsAdpVideoComponent: VideoComponent; 15 | public cldAnalyticsCustomDataObjectVideoComponent: VideoComponent; 16 | public cldAnalyticsCustomDataFunctionVideoComponent: VideoComponent; 17 | 18 | constructor(page: Page) { 19 | super(page); 20 | this.cldAnalyticsVideoComponent = new VideoComponent(page, CLD_ANALYTICS_PAGE_VIDEO_SELECTOR); 21 | this.cldAnalyticsAdpVideoComponent = new VideoComponent(page, CLD_ANALYTICS_PAGE_ADP_VIDEO_SELECTOR); 22 | this.cldAnalyticsCustomDataObjectVideoComponent = new VideoComponent(page, CLD_ANALYTICS_PAGE_CUSTOM_DATA_OBJECT_VIDEO_SELECTOR); 23 | this.cldAnalyticsCustomDataFunctionVideoComponent = new VideoComponent(page, CLD_ANALYTICS_PAGE_CUSTOM_DATA_FUNCTION_VIDEO_SELECTOR); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/e2e/src/pom/codecsAndFormats.ts: -------------------------------------------------------------------------------- 1 | import { Page } from '@playwright/test'; 2 | import { VideoComponent } from '../../components/videoComponent'; 3 | import { BasePage } from './BasePage'; 4 | const CODECS_AND_FORMAT_PAGE_F_AUTO_VIDEO_SELECTOR = '//*[@id="player_html5_api"]'; 5 | const CODECS_AND_FORMAT_PAGE_AV1_VIDEO_SELECTOR = '//*[@id="player-av1_html5_api"]'; 6 | const CODECS_AND_FORMAT_PAGE_VP9_VIDEO_SELECTOR = '//*[@id="player-vp9_html5_api"]'; 7 | 8 | /** 9 | * Video player examples chapters page object 10 | */ 11 | export class CodecsAndFormats extends BasePage { 12 | public codecsAndFormatsFAutoVideoComponent: VideoComponent; 13 | public codecsAndFormatsAv1VideoComponent: VideoComponent; 14 | public codecsAndFormatsVp9VideoComponent: VideoComponent; 15 | 16 | constructor(page: Page) { 17 | super(page); 18 | this.codecsAndFormatsFAutoVideoComponent = new VideoComponent(page, CODECS_AND_FORMAT_PAGE_F_AUTO_VIDEO_SELECTOR); 19 | this.codecsAndFormatsAv1VideoComponent = new VideoComponent(page, CODECS_AND_FORMAT_PAGE_AV1_VIDEO_SELECTOR); 20 | this.codecsAndFormatsVp9VideoComponent = new VideoComponent(page, CODECS_AND_FORMAT_PAGE_VP9_VIDEO_SELECTOR); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/e2e/src/pom/colorsApiPage.ts: -------------------------------------------------------------------------------- 1 | import { Page } from '@playwright/test'; 2 | import { VideoComponent } from '../../components/videoComponent'; 3 | import { BasePage } from './BasePage'; 4 | const COLORS_API_PAGE_COLOR_SKIN_VIDEO_SELECTOR = '//*[@id="player-1_html5_api"]'; 5 | const COLORS_API_PAGE_DARK_SKIN_VIDEO_SELECTOR = '//*[@id="player-2_html5_api"]'; 6 | const COLORS_API_PAGE_LIGHT_SKIN_VIDEO_SELECTOR = '//*[@id="player-3_html5_api"]'; 7 | 8 | /** 9 | * Video player examples colors API page object 10 | */ 11 | export class ColorsApiPage extends BasePage { 12 | public colorsApiColorSkinVideoComponent: VideoComponent; 13 | public colorsApiDarkSkinVideoComponent: VideoComponent; 14 | public colorsApiLightSkinVideoComponent: VideoComponent; 15 | 16 | constructor(page: Page) { 17 | super(page); 18 | this.colorsApiColorSkinVideoComponent = new VideoComponent(page, COLORS_API_PAGE_COLOR_SKIN_VIDEO_SELECTOR); 19 | this.colorsApiDarkSkinVideoComponent = new VideoComponent(page, COLORS_API_PAGE_DARK_SKIN_VIDEO_SELECTOR); 20 | this.colorsApiLightSkinVideoComponent = new VideoComponent(page, COLORS_API_PAGE_LIGHT_SKIN_VIDEO_SELECTOR); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/e2e/src/pom/componentsPage.ts: -------------------------------------------------------------------------------- 1 | import { Page } from '@playwright/test'; 2 | import { VideoComponent } from '../../components/videoComponent'; 3 | import { BasePage } from './BasePage'; 4 | const COMPONENTS_PAGE_VIDEO_SELECTOR = '//*[@id="player_html5_api"]'; 5 | 6 | /** 7 | * Video player examples components page object 8 | */ 9 | export class ComponentsPage extends BasePage { 10 | public componentsVideoComponent: VideoComponent; 11 | 12 | constructor(page: Page) { 13 | super(page); 14 | this.componentsVideoComponent = new VideoComponent(page, COMPONENTS_PAGE_VIDEO_SELECTOR); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/e2e/src/pom/displayConfigurationsPage.ts: -------------------------------------------------------------------------------- 1 | import { Page } from '@playwright/test'; 2 | import { VideoComponent } from '../../components/videoComponent'; 3 | import { BasePage } from './BasePage'; 4 | const DISPLAY_CONFIGURATIONS_PAGE_VIDEO_SELECTOR = '//*[@id="player_html5_api"]'; 5 | 6 | /** 7 | * Video player examples components page object 8 | */ 9 | export class DisplayConfigurationsPage extends BasePage { 10 | public displayConfigurationsPageVideoComponent: VideoComponent; 11 | 12 | constructor(page: Page) { 13 | super(page); 14 | this.displayConfigurationsPageVideoComponent = new VideoComponent(page, DISPLAY_CONFIGURATIONS_PAGE_VIDEO_SELECTOR); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/e2e/src/pom/floatingPlayerPgae.ts: -------------------------------------------------------------------------------- 1 | import { Page } from '@playwright/test'; 2 | import { VideoComponent } from '../../components/videoComponent'; 3 | import { BasePage } from './BasePage'; 4 | const FLOATING_PLAYER_PAGE_VIDEO_SELECTOR = '//*[@id="player_html5_api"]'; 5 | 6 | /** 7 | * Video player examples Floating player page object 8 | */ 9 | export class FloatingPlayerPage extends BasePage { 10 | public floatingPlayerVideoComponent: VideoComponent; 11 | 12 | constructor(page: Page) { 13 | super(page); 14 | this.floatingPlayerVideoComponent = new VideoComponent(page, FLOATING_PLAYER_PAGE_VIDEO_SELECTOR); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/e2e/src/pom/fluidLayoutsPage.ts: -------------------------------------------------------------------------------- 1 | import { Page } from '@playwright/test'; 2 | import { VideoComponent } from '../../components/videoComponent'; 3 | import { BasePage } from './BasePage'; 4 | const FLUID_LAYOUTS_PAGE_VIDEO_SELECTOR = '//*[@id="player_html5_api"]'; 5 | 6 | /** 7 | * Video player examples fluid layouts page object 8 | */ 9 | export class FluidLayoutsPage extends BasePage { 10 | public fluidLayoutsVideoComponent: VideoComponent; 11 | 12 | constructor(page: Page) { 13 | super(page); 14 | this.fluidLayoutsVideoComponent = new VideoComponent(page, FLUID_LAYOUTS_PAGE_VIDEO_SELECTOR); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/e2e/src/pom/forceHlsSubtitlesPage.ts: -------------------------------------------------------------------------------- 1 | import { Page } from '@playwright/test'; 2 | import { VideoComponent } from '../../components/videoComponent'; 3 | import { BasePage } from './BasePage'; 4 | const FORCE_HLS_SUBTITLES_PAGE_VIDEO_SELECTOR = '//*[@id="player_html5_api"]'; 5 | 6 | /** 7 | * Video player examples force HLS subtitles page object 8 | */ 9 | export class ForceHlsSubtitlesPage extends BasePage { 10 | public forceHlsSubtitlesVideoComponent: VideoComponent; 11 | 12 | constructor(page: Page) { 13 | super(page); 14 | this.forceHlsSubtitlesVideoComponent = new VideoComponent(page, FORCE_HLS_SUBTITLES_PAGE_VIDEO_SELECTOR); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/e2e/src/pom/highlightsGraphPage.ts: -------------------------------------------------------------------------------- 1 | import { Page } from '@playwright/test'; 2 | import { VideoComponent } from '../../components/videoComponent'; 3 | import { BasePage } from './BasePage'; 4 | const HIGHLIGHTS_GRAPH_PAGE_VIDEO_SELECTOR = '//*[@id="player_html5_api"]'; 5 | 6 | /** 7 | * Video player examples highlight graph page object 8 | */ 9 | export class HighlightsGraphPage extends BasePage { 10 | public videoHighlightsGraphPage: VideoComponent; 11 | 12 | constructor(page: Page) { 13 | super(page); 14 | this.videoHighlightsGraphPage = new VideoComponent(page, HIGHLIGHTS_GRAPH_PAGE_VIDEO_SELECTOR); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/e2e/src/pom/mainPage.ts: -------------------------------------------------------------------------------- 1 | import { Page } from '@playwright/test'; 2 | import { VideoComponent } from '../../components/videoComponent'; 3 | import { BasePage } from './BasePage'; 4 | const MAIN_PAGE_VIDEO_SELECTOR = '//*[@id="player_html5_api"]'; 5 | 6 | /** 7 | * Video player examples main page object 8 | */ 9 | export class MainPage extends BasePage { 10 | public videoMainPage: VideoComponent; 11 | 12 | constructor(page: Page) { 13 | super(page); 14 | this.videoMainPage = new VideoComponent(page, MAIN_PAGE_VIDEO_SELECTOR); 15 | } 16 | 17 | /** 18 | * Click links by given name. 19 | */ 20 | public async clickLinkByName(name: string): Promise { 21 | await this.page.getByRole('link', { name, exact: true }).click(); 22 | await this.page.waitForLoadState('load'); 23 | } 24 | 25 | /** 26 | * Navigate to local examples page. 27 | */ 28 | public async goto(): Promise { 29 | await this.page.goto('http://localhost:3000/index.html'); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/e2e/src/pom/multiplePlayersPage.ts: -------------------------------------------------------------------------------- 1 | import { Page } from '@playwright/test'; 2 | import { VideoComponent } from '../../components/videoComponent'; 3 | import { BasePage } from './BasePage'; 4 | const MULTIPLE_PLAYERS_PAGE_PLAYER_1_VIDEO_SELECTOR = '//*[@id="player-1_html5_api"]'; 5 | const MULTIPLE_PLAYERS_PAGE_PLAYER_2_VIDEO_SELECTOR = '//*[@id="player-2_html5_api"]'; 6 | const MULTIPLE_PLAYERS_PAGE_PLAYER_3_VIDEO_SELECTOR = '//*[@id="player-3_html5_api"]'; 7 | 8 | /** 9 | * Video player examples colors API page object 10 | */ 11 | export class MultiplePlayersPage extends BasePage { 12 | public multiplePlayersPlayer1VideoComponent: VideoComponent; 13 | public multiplePlayersPlayer2VideoComponent: VideoComponent; 14 | public multiplePlayersPlayer3VideoComponent: VideoComponent; 15 | 16 | constructor(page: Page) { 17 | super(page); 18 | this.multiplePlayersPlayer1VideoComponent = new VideoComponent(page, MULTIPLE_PLAYERS_PAGE_PLAYER_1_VIDEO_SELECTOR); 19 | this.multiplePlayersPlayer2VideoComponent = new VideoComponent(page, MULTIPLE_PLAYERS_PAGE_PLAYER_2_VIDEO_SELECTOR); 20 | this.multiplePlayersPlayer3VideoComponent = new VideoComponent(page, MULTIPLE_PLAYERS_PAGE_PLAYER_3_VIDEO_SELECTOR); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/e2e/src/pom/playlistByTagPage.ts: -------------------------------------------------------------------------------- 1 | import { Page } from '@playwright/test'; 2 | import { VideoComponent } from '../../components/videoComponent'; 3 | import { BasePage } from './BasePage'; 4 | const PLAYLIST_BY_TAG_PAGE_VIDEO_SELECTOR = '//*[@id="player_html5_api"]'; 5 | 6 | /** 7 | * Video player examples playlist by tag page object 8 | */ 9 | export class PlaylistByTagPage extends BasePage { 10 | public playlistByTagVideoComponent: VideoComponent; 11 | 12 | constructor(page: Page) { 13 | super(page); 14 | this.playlistByTagVideoComponent = new VideoComponent(page, PLAYLIST_BY_TAG_PAGE_VIDEO_SELECTOR); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/e2e/src/pom/playlistPage.ts: -------------------------------------------------------------------------------- 1 | import { Page } from '@playwright/test'; 2 | import { VideoComponent } from '../../components/videoComponent'; 3 | import { BasePage } from './BasePage'; 4 | const PLAYLIST_PAGE_HORIZONTAL_VIDEO_SELECTOR = '//*[@id="player-horizontal_html5_api"]'; 5 | const PLAYLIST_PAGE_VERTICAL_VIDEO_SELECTOR = '//*[@id="player-vertical_html5_api"]'; 6 | 7 | /** 8 | * Video player examples playlist page object 9 | */ 10 | export class PlaylistPage extends BasePage { 11 | public playlistHorizontalVideoComponent: VideoComponent; 12 | public playlistVerticalVideoComponent: VideoComponent; 13 | 14 | constructor(page: Page) { 15 | super(page); 16 | this.playlistHorizontalVideoComponent = new VideoComponent(page, PLAYLIST_PAGE_HORIZONTAL_VIDEO_SELECTOR); 17 | this.playlistVerticalVideoComponent = new VideoComponent(page, PLAYLIST_PAGE_VERTICAL_VIDEO_SELECTOR); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/e2e/src/pom/posterOptionsPage.ts: -------------------------------------------------------------------------------- 1 | import { Page } from '@playwright/test'; 2 | import { VideoComponent } from '../../components/videoComponent'; 3 | import { BasePage } from './BasePage'; 4 | const POSTER_OPTIONS_PAGE_CUSTOM_IMAGE_VIDEO_SELECTOR = '//*[@id="player-image-poster_html5_api"]'; 5 | const POSTER_OPTIONS_PAGE_SPECIFIC_FRAME_VIDEO_SELECTOR = '//*[@id="player-frame-0_html5_api"]'; 6 | const POSTER_OPTIONS_PAGE_TRANSFORMATIONS_ARRAY_VIDEO_SELECTOR = '//*[@id="player-poster-options_html5_api"]'; 7 | const POSTER_OPTIONS_PAGE_RAW_URL_NO_POSTER_VIDEO_SELECTOR = '//*[@id="player-raw_html5_api"]'; 8 | 9 | /** 10 | * Video player examples poster options page object 11 | */ 12 | export class PosterOptionsPage extends BasePage { 13 | public posterOptionsCustomImageVideoComponent: VideoComponent; 14 | public posterOptionsSpecificFrameVideoComponent: VideoComponent; 15 | public posterOptionsTransformationsArrayVideoComponent: VideoComponent; 16 | public posterOptionsRawUrlNoPosterVideoComponent: VideoComponent; 17 | 18 | constructor(page: Page) { 19 | super(page); 20 | this.posterOptionsCustomImageVideoComponent = new VideoComponent(page, POSTER_OPTIONS_PAGE_CUSTOM_IMAGE_VIDEO_SELECTOR); 21 | this.posterOptionsSpecificFrameVideoComponent = new VideoComponent(page, POSTER_OPTIONS_PAGE_SPECIFIC_FRAME_VIDEO_SELECTOR); 22 | this.posterOptionsTransformationsArrayVideoComponent = new VideoComponent(page, POSTER_OPTIONS_PAGE_TRANSFORMATIONS_ARRAY_VIDEO_SELECTOR); 23 | this.posterOptionsRawUrlNoPosterVideoComponent = new VideoComponent(page, POSTER_OPTIONS_PAGE_RAW_URL_NO_POSTER_VIDEO_SELECTOR); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/e2e/src/pom/profilesPage.ts: -------------------------------------------------------------------------------- 1 | import { Page } from '@playwright/test'; 2 | import { VideoComponent } from '../../components/videoComponent'; 3 | import { BasePage } from './BasePage'; 4 | const PROFILES_PAGE_DEFAULT_PROFILE_VIDEO_SELECTOR = '//*[@id="player-default-profile_html5_api"]'; 5 | const PROFILES_PAGE_CUSTOM_PROFILE_VIDEO_SELECTOR = '//*[@id="player-custom-profile_html5_api"]'; 6 | const PROFILES_PAGE_CUSTOM_PROFILE_OVERRIDES_VIDEO_SELECTOR = '//*[@id="player-custom-profile-overrides_html5_api"]'; 7 | 8 | /** 9 | * Video player examples profiles page object 10 | */ 11 | export class ProfilesPage extends BasePage { 12 | public profilesDefaultProfileVideoComponent: VideoComponent; 13 | public profilesCustomProfileVideoComponent: VideoComponent; 14 | public profilesCustomProfileOverridesVideoComponent: VideoComponent; 15 | 16 | constructor(page: Page) { 17 | super(page); 18 | this.profilesDefaultProfileVideoComponent = new VideoComponent(page, PROFILES_PAGE_DEFAULT_PROFILE_VIDEO_SELECTOR); 19 | this.profilesCustomProfileVideoComponent = new VideoComponent(page, PROFILES_PAGE_CUSTOM_PROFILE_VIDEO_SELECTOR); 20 | this.profilesCustomProfileOverridesVideoComponent = new VideoComponent(page, PROFILES_PAGE_CUSTOM_PROFILE_OVERRIDES_VIDEO_SELECTOR); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/e2e/src/pom/rawUrlPage.ts: -------------------------------------------------------------------------------- 1 | import { Page } from '@playwright/test'; 2 | import { VideoComponent } from '../../components/videoComponent'; 3 | import { BasePage } from './BasePage'; 4 | const RAW_URL_PAGE_VIDEO_SELECTOR = '//*[@id="player_html5_api"]'; 5 | const RAW_URL_PAGE_ADAPTIVE_VIDEO_SELECTOR = '//*[@id="adpPlayer_html5_api"]'; 6 | 7 | /** 8 | * Video player examples raw URL page object 9 | */ 10 | export class RawUrlPage extends BasePage { 11 | public rawUrlVideoComponent: VideoComponent; 12 | public rawUrlAdaptiveVideoComponent: VideoComponent; 13 | 14 | constructor(page: Page) { 15 | super(page); 16 | this.rawUrlVideoComponent = new VideoComponent(page, RAW_URL_PAGE_VIDEO_SELECTOR); 17 | this.rawUrlAdaptiveVideoComponent = new VideoComponent(page, RAW_URL_PAGE_ADAPTIVE_VIDEO_SELECTOR); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/e2e/src/pom/recommendationsPage.ts: -------------------------------------------------------------------------------- 1 | import { Page } from '@playwright/test'; 2 | import { VideoComponent } from '../../components/videoComponent'; 3 | import { BasePage } from './BasePage'; 4 | const RECOMMENDATIONS_PAGE_VIDEO_SELECTOR = '//*[@id="player_html5_api"]'; 5 | 6 | /** 7 | * Video player examples recommendations page object 8 | */ 9 | export class RecommendationsPage extends BasePage { 10 | public recommendationsVideoComponent: VideoComponent; 11 | 12 | constructor(page: Page) { 13 | super(page); 14 | this.recommendationsVideoComponent = new VideoComponent(page, RECOMMENDATIONS_PAGE_VIDEO_SELECTOR); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/e2e/src/pom/seekThumbnailsPage.ts: -------------------------------------------------------------------------------- 1 | import { Page } from '@playwright/test'; 2 | import { VideoComponent } from '../../components/videoComponent'; 3 | import { BasePage } from './BasePage'; 4 | const SEEK_THUMBNAILS_PAGE_VIDEO_SELECTOR = '//*[@id="player_html5_api"]'; 5 | 6 | /** 7 | * Video player examples seek thumbnails page object 8 | */ 9 | export class SeekThumbnailsPage extends BasePage { 10 | public seekThumbnailsVideoComponent: VideoComponent; 11 | 12 | constructor(page: Page) { 13 | super(page); 14 | this.seekThumbnailsVideoComponent = new VideoComponent(page, SEEK_THUMBNAILS_PAGE_VIDEO_SELECTOR); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/e2e/src/pom/shoppableVideosPage.ts: -------------------------------------------------------------------------------- 1 | import { Page } from '@playwright/test'; 2 | import { VideoComponent } from '../../components/videoComponent'; 3 | import { BasePage } from './BasePage'; 4 | const SHOPPABLE_VIDEOS_PAGE_VIDEO_SELECTOR = '//*[@id="player_html5_api"]'; 5 | 6 | /** 7 | * Video player examples shoppable vidoes page object 8 | */ 9 | export class ShoppableVideosPage extends BasePage { 10 | public shoppableVideosVideoComponent: VideoComponent; 11 | 12 | constructor(page: Page) { 13 | super(page); 14 | this.shoppableVideosVideoComponent = new VideoComponent(page, SHOPPABLE_VIDEOS_PAGE_VIDEO_SELECTOR); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/e2e/src/pom/subtitlesAndCaptionsPage.ts: -------------------------------------------------------------------------------- 1 | import { Page } from '@playwright/test'; 2 | import { VideoComponent } from '../../components/videoComponent'; 3 | import { BasePage } from './BasePage'; 4 | const SRT_AND_VTT_VIDEO_SELECTOR = '//*[@id="player_html5_api"]'; 5 | const PLAYLIST_SUBTITLES_VIDEO_SELECTOR = '//*[@id="playlist_html5_api"]'; 6 | const PACED_STYLES_CAPTIONS_VIDEO_SELECTOR = '//*[@id="paced_html5_api"]'; 7 | const KARAOKE_VIDEO_SELECTOR = '//*[@id="karaoke_html5_api"]'; 8 | const TRANSLATED_TRANSCRIPT_VIDEO_SELECTOR = '//*[@id="translated-transcript_html5_api"]'; 9 | /** 10 | * Video player examples subtitles and captions page object 11 | */ 12 | export class SubtitlesAndCaptionsPage extends BasePage { 13 | public srtAndVttVideoComponent: VideoComponent; 14 | public playlistSubtitlesVideoComponent: VideoComponent; 15 | public pacedStyledVideoComponent: VideoComponent; 16 | public karaokeVideoComponent: VideoComponent; 17 | public translatedTranscriptVideoComponent: VideoComponent; 18 | 19 | constructor(page: Page) { 20 | super(page); 21 | this.srtAndVttVideoComponent = new VideoComponent(page, SRT_AND_VTT_VIDEO_SELECTOR); 22 | this.playlistSubtitlesVideoComponent = new VideoComponent(page, PLAYLIST_SUBTITLES_VIDEO_SELECTOR); 23 | this.pacedStyledVideoComponent = new VideoComponent(page, PACED_STYLES_CAPTIONS_VIDEO_SELECTOR); 24 | this.karaokeVideoComponent = new VideoComponent(page, KARAOKE_VIDEO_SELECTOR); 25 | this.translatedTranscriptVideoComponent = new VideoComponent(page, TRANSLATED_TRANSCRIPT_VIDEO_SELECTOR); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/e2e/src/pom/vastAndVpaidPage.ts: -------------------------------------------------------------------------------- 1 | import { Page } from '@playwright/test'; 2 | import { VideoComponent } from '../../components/videoComponent'; 3 | import { BasePage } from './BasePage'; 4 | const SINGLE_VIDEO_WITH_ADS_VIDEO_SELECTOR = '//*[@id="player_html5_api"]'; 5 | const PLAYLIST_WITH_ADS_VIDEO_SELECTOR = '//*[@id="player-playlist_html5_api"]'; 6 | 7 | /** 8 | * Video player examples vast and vpaid page object 9 | */ 10 | export class VastAndVpaidPage extends BasePage { 11 | public singleVideoWithAdsVideoComponent: VideoComponent; 12 | public playlistWithAdsVideoComponent: VideoComponent; 13 | 14 | constructor(page: Page) { 15 | super(page); 16 | this.singleVideoWithAdsVideoComponent = new VideoComponent(page, SINGLE_VIDEO_WITH_ADS_VIDEO_SELECTOR); 17 | this.playlistWithAdsVideoComponent = new VideoComponent(page, PLAYLIST_WITH_ADS_VIDEO_SELECTOR); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/e2e/src/pom/videoTransformationsPage.ts: -------------------------------------------------------------------------------- 1 | import { Page } from '@playwright/test'; 2 | import { VideoComponent } from '../../components/videoComponent'; 3 | import { BasePage } from './BasePage'; 4 | const VIA_SOURCE_VIDEO_SELECTOR = '//*[@id="player-1_html5_api"]'; 5 | const VIA_PLAYER_VIDEO_SELECTOR = '//*[@id="player-2_html5_api"]'; 6 | const VIA_DATA_CLD_TRANSFORMATIONS_VIDEO_SELECTOR = '//*[@id="player-3_html5_api"]'; 7 | 8 | /** 9 | * Video player examples video transformations page object 10 | */ 11 | export class VideoTransformationsPage extends BasePage { 12 | public viaSourceVideoComponent: VideoComponent; 13 | public viaPlayerVideoComponent: VideoComponent; 14 | public viaDataCldTransformationsVideoComponent: VideoComponent; 15 | 16 | constructor(page: Page) { 17 | super(page); 18 | this.viaSourceVideoComponent = new VideoComponent(page, VIA_SOURCE_VIDEO_SELECTOR); 19 | this.viaPlayerVideoComponent = new VideoComponent(page, VIA_PLAYER_VIDEO_SELECTOR); 20 | this.viaDataCldTransformationsVideoComponent = new VideoComponent(page, VIA_DATA_CLD_TRANSFORMATIONS_VIDEO_SELECTOR); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/e2e/src/pom/visualSearchPage.ts: -------------------------------------------------------------------------------- 1 | import { Page } from '@playwright/test'; 2 | import { VideoComponent } from '../../components/videoComponent'; 3 | import { BasePage } from './BasePage'; 4 | 5 | const VISUAL_SEARCH_PAGE_VIDEO_SELECTOR = '//*[@id="player_html5_api"]'; 6 | const VISUAL_SEARCH_PLAYLIST_VIDEO_SELECTOR = '//*[@id="player-playlist_html5_api"]'; 7 | 8 | /** 9 | * Video player examples visual search page object 10 | */ 11 | export class VisualSearchPage extends BasePage { 12 | public visualSearchVideoComponent: VideoComponent; 13 | public visualSearchPlaylistVideoComponent: VideoComponent; 14 | 15 | constructor(page: Page) { 16 | super(page); 17 | this.visualSearchVideoComponent = new VideoComponent(page, VISUAL_SEARCH_PAGE_VIDEO_SELECTOR); 18 | this.visualSearchPlaylistVideoComponent = new VideoComponent(page, VISUAL_SEARCH_PLAYLIST_VIDEO_SELECTOR); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/e2e/src/pom/vr360VideosPage.ts: -------------------------------------------------------------------------------- 1 | import { Page } from '@playwright/test'; 2 | import { VideoComponent } from '../../components/videoComponent'; 3 | import { BasePage } from './BasePage'; 4 | const VR_360_VIDEO_SELECTOR = '//*[@id="player_html5_api"]'; 5 | 6 | /** 7 | * Video player examples VR 360 videos page object 8 | */ 9 | export class Vr360VideosPage extends BasePage { 10 | public vr360VideoComponent: VideoComponent; 11 | 12 | constructor(page: Page) { 13 | super(page); 14 | this.vr360VideoComponent = new VideoComponent(page, VR_360_VIDEO_SELECTOR); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/e2e/testData/ExampleLinkNames.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Enum representing the names of example pages. 3 | */ 4 | export enum ExampleLinkName { 5 | AdaptiveStreaming = 'Adaptive Streaming', 6 | AIHighlightsGraph = 'AI Highlights Graph', 7 | Analytics = 'Analytics', 8 | APIAndEvents = 'API and Events', 9 | AudioPlayer = 'Audio Player', 10 | AutoplayOnScroll = 'Autoplay on Scroll', 11 | Chapters = 'Chapters', 12 | CloudinaryAnalytics = 'Cloudinary Analytics', 13 | CodecsAndFormats = 'Codecs and formats', 14 | ColorsAPI = 'Colors API', 15 | Components = 'Components', 16 | CustomErrors = 'Custom Errors', 17 | DisplayConfigurations = 'Display Configurations', 18 | DebugMode = 'Debug mode', 19 | FloatingPlayer = 'Floating Player', 20 | FluidLayouts = 'Fluid Layouts', 21 | ForceHLSSubtitles = 'Force HLS Subtitles', 22 | InteractionArea = 'Interaction Area', 23 | MultiplePlayers = 'Multiple Players', 24 | Playlist = 'Playlist', 25 | PlaylistByTag = 'Playlist by Tag', 26 | PosterOptions = 'Poster Options', 27 | Profiles = 'Profiles', 28 | RawURL = 'Raw URL', 29 | Recommendations = 'Recommendations', 30 | SeekThumbnails = 'Seek Thumbnails', 31 | ShoppableVideos = 'Shoppable Videos', 32 | SubtitlesAndCaptions = 'Subtitles & Captions', 33 | VideoTransformations = 'Video Transformations', 34 | VASTAndVPAIDSupport = 'VAST & VPAID Support', 35 | VR360Videos = 'VR/360 Videos', 36 | EmbeddedIframePlayer = 'Embedded (iframe) player', 37 | ESMImports = 'ESM Imports', 38 | AllBuild = '/all build', 39 | VisualSearch = 'Visual Search', 40 | } 41 | -------------------------------------------------------------------------------- /test/e2e/testData/esmUrl.ts: -------------------------------------------------------------------------------- 1 | // On PR level it will use the preview deploy URL and locally it will use the latest EDGE. 2 | export const ESM_URL = process.env.PREVIEW_URL ?? 'https://cld-vp-esm-pages.netlify.app/'; 3 | -------------------------------------------------------------------------------- /test/e2e/types/exampleLinkType.ts: -------------------------------------------------------------------------------- 1 | import { ExampleLinkName } from '../testData/ExampleLinkNames'; 2 | 3 | /** 4 | * Example links type 5 | */ 6 | export type ExampleLinkType = { name: ExampleLinkName; endpoint: string }; 7 | -------------------------------------------------------------------------------- /test/fluid.test.js: -------------------------------------------------------------------------------- 1 | describe('Fluid tests', () => { 2 | 3 | beforeEach(async () => { 4 | await page.setViewport({width: 1280, height: 800}); 5 | await page.goto('http://localhost:3000/fluid.html', {waitUntil: 'load'}); 6 | await page.evaluate(() => { 7 | Object.defineProperty(HTMLMediaElement.prototype, 'playing', { 8 | get: function() { 9 | return !!(this.currentTime > 0 && !this.paused && !this.ended && 10 | this.readyState > 2); 11 | } 12 | }); 13 | }); 14 | }, 10000); 15 | 16 | it('Test fluid change', async () => { 17 | await page.waitFor(1000); 18 | const origWidth = await page.$eval('#player_html5_api', p => p.clientWidth); 19 | await page.setViewport({ width: 800, height: 800 }); 20 | await page.waitFor(1000); 21 | const currWidth = await page.$eval('#player_html5_api', p => p.clientWidth); 22 | expect(origWidth).toBeGreaterThan(currWidth); 23 | }); 24 | 25 | it('Test no fluid change', async () => { 26 | await page.waitFor(1000); 27 | await page.click('#toggle-fluid'); 28 | await page.waitFor(500); 29 | const origWidth = await page.$eval('#player_html5_api', p => p.clientWidth); 30 | await page.setViewport({ width: 800, height: 800 }); 31 | await page.waitFor(1000); 32 | const currWidth = await page.$eval('#player_html5_api', p => p.clientWidth); 33 | expect(origWidth).toEqual(currWidth); 34 | }); 35 | 36 | }); 37 | -------------------------------------------------------------------------------- /test/mocks/cloudinary-core-mock.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudinary/cloudinary-video-player/847f64997b6c8527858e16c45f718fcf9da2ff46/test/mocks/cloudinary-core-mock.js -------------------------------------------------------------------------------- /test/mocks/styleMock.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; 2 | -------------------------------------------------------------------------------- /test/multiplayer.test.js: -------------------------------------------------------------------------------- 1 | describe('Multi-player tests', () => { 2 | 3 | beforeEach(async () => { 4 | await page.setViewport({ width: 1280, height: 1800 }); 5 | await page.goto('http://localhost:3000/multiple-players.html', { waitUntil: 'load' }); 6 | await page.evaluate(() => { 7 | Object.defineProperty(HTMLMediaElement.prototype, 'playing', { 8 | get: function() { 9 | return !!(this.currentTime > 0 && !this.paused && !this.ended && 10 | this.readyState > 2); 11 | } 12 | }); 13 | }); 14 | }, 10000); 15 | 16 | it('Test play', async () => { 17 | page.waitFor(1500); 18 | const players = await page.$$eval('video', v => v); 19 | for (const player of players) { 20 | const id = '#' + player.playerId + '_html5_api'; 21 | await page.waitFor(1500); 22 | expect(await page.$eval(id, el => el.playing)).toBe(true); 23 | } 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /test/puppeteer/vp-env.js: -------------------------------------------------------------------------------- 1 | const PuppeteerEnvironment = require('jest-environment-puppeteer'); 2 | 3 | class VideoPlayerEnvironment extends PuppeteerEnvironment { 4 | async setup() { 5 | await super.setup(); 6 | // Your setup 7 | } 8 | 9 | async teardown() { 10 | // Your teardown 11 | try { 12 | await super.teardown(); 13 | } catch (e) { 14 | console.log(e); 15 | } 16 | } 17 | } 18 | 19 | module.exports = VideoPlayerEnvironment; 20 | -------------------------------------------------------------------------------- /test/title-bar.test.js: -------------------------------------------------------------------------------- 1 | describe('basic player tests', () => { 2 | beforeAll(async () => { 3 | jest.setTimeout(35000); 4 | await page.setViewport({width: 1280, height: 800}); 5 | await page.goto('http://localhost:3000/', {waitUntil: 'load'}); 6 | await page.evaluate(() => { 7 | Object.defineProperty(HTMLMediaElement.prototype, 'playing', { 8 | get: function() { 9 | return !!(this.currentTime > 0 && !this.paused && !this.ended && 10 | this.readyState > 2); 11 | } 12 | }); 13 | }); 14 | }, 10000); 15 | it('Test title bar title', async () => { 16 | let ds = JSON.parse(await page.$eval('#player', v => v.getAttribute('data-cld-source'))); 17 | let titlebarTitle = await page.$eval('#player > .vjs-title-bar > .vjs-title-bar-title', t => t.textContent); 18 | await expect(ds.info.title).toEqual(titlebarTitle); 19 | }); 20 | it('Test title bar subtitle', async () => { 21 | let ds = JSON.parse(await page.$eval('#player', v => v.getAttribute('data-cld-source'))); 22 | let sub = await page.$eval('#player > .vjs-title-bar > .vjs-title-bar-subtitle', t => t.textContent); 23 | await expect(ds.info.subtitle).toEqual(sub); 24 | }); 25 | 26 | }); 27 | 28 | 29 | -------------------------------------------------------------------------------- /test/unit/cloudinaryConfig.test.js: -------------------------------------------------------------------------------- 1 | import '../../src/'; 2 | import VideoPlayer from '../../src/video-player'; 3 | 4 | describe('secure true test', () => { 5 | it('test force secure true', async () => { 6 | jest.useFakeTimers(); 7 | document.body.innerHTML = '
'; 8 | const vp = new VideoPlayer('test', { hideContextMenu: true, cloudinaryConfig: { cloud_name: 'demo' } }, false); 9 | const conf = vp.videojs.cloudinary.cloudinaryConfig(); 10 | expect(conf.secure).toEqual(true); 11 | }); 12 | it('test explicit secure false', async () => { 13 | jest.useFakeTimers(); 14 | document.body.innerHTML = '
'; 15 | const vp = new VideoPlayer('test', { hideContextMenu: true, cloudinaryConfig: { cloud_name: 'demo', secure: false } }, false); 16 | const conf = vp.videojs.cloudinary.cloudinaryConfig(); 17 | expect(conf.secure).toEqual(false); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /test/unit/cloudinaryUtils.test.js: -------------------------------------------------------------------------------- 1 | const tracks = [ 2 | { 3 | default: false, 4 | kind: 'subtitles', 5 | label: 'German subtitles', 6 | src: 'https://res.cloudinary.com/yaronr/raw/upload/v1558966008/Meetup_german.vtt', 7 | srclang: 'de' 8 | }, 9 | { 10 | default: false, 11 | kind: 'subtitles', 12 | label: 'Hebrew subtitles', 13 | src: 'https://res.cloudinary.com/yaronr/raw/upload/v1558966008/Meetup_hebrew.vtt', 14 | srclang: 'he' 15 | }, 16 | { 17 | default: false, 18 | kind: 'subtitles', 19 | label: 'Swedish subtitles', 20 | src: 'https://res.cloudinary.com/yaronr/raw/upload/v1558966008/Meetup_swedish.vtt', 21 | srclang: 'se' 22 | } 23 | ]; 24 | 25 | global.fetch = jest.fn((url, options) => { 26 | return new Promise((resolve) => { 27 | if (url === tracks[2].src) { 28 | resolve({ status: 404 }); 29 | } else { 30 | resolve({ status: 200 }); 31 | } 32 | }); 33 | }); 34 | 35 | import { addTextTracks } from '../../src/utils/cloudinary'; 36 | 37 | describe('video source tests', () => { 38 | it('test filter out bad vtt', async () => { 39 | let vjs = jest.createMockFromModule('video.js'); 40 | vjs.addRemoteTextTrack = jest.fn(); 41 | jest.spyOn(vjs, 'addRemoteTextTrack'); 42 | addTextTracks(tracks, vjs); 43 | setTimeout(() => { 44 | expect(vjs.addRemoteTextTrack).toBeCalledTimes(2); 45 | }, 1000); 46 | }); 47 | 48 | }); 49 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "noImplicitAny": true, 5 | "esModuleInterop": true, 6 | "allowSyntheticDefaultImports": true, 7 | "declaration": true, 8 | "declarationDir": "./types" 9 | }, 10 | "include": [ 11 | "dist/**/*", 12 | "lib/**/*", 13 | "types/**/*" 14 | ], 15 | "exclude": [ 16 | "node_modules" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /types/cld-video-player-tests.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is used to validate the type spec. The code does not actually run. 3 | */ 4 | 5 | import cloudinary, { videoPlayer, videoPlayerWithProfile, VideoPlayer } from './cld-video-player'; 6 | 7 | const player: VideoPlayer = cloudinary.videoPlayer('player', {}, () => {}); 8 | 9 | player.source('test', { 10 | sourceTypes: ['mp4/h264'] 11 | }); 12 | player.play(); 13 | player.pause(); 14 | 15 | const vPlayer: VideoPlayer = videoPlayer('player', {}); 16 | 17 | vPlayer.source('test'); 18 | 19 | async () => { 20 | const profilePlayer = await videoPlayerWithProfile('player', { 21 | constrols: false, 22 | fontFace: 'Merienda' 23 | }); 24 | 25 | profilePlayer.source({ 26 | publicId: 'elephants', 27 | profile: 'default' 28 | }); 29 | }; 30 | -------------------------------------------------------------------------------- /webpack/analyzer.config.js: -------------------------------------------------------------------------------- 1 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; 2 | const { merge } = require('webpack-merge'); 3 | const buildConf = require('./build.config'); 4 | 5 | module.exports = merge(buildConf, { 6 | plugins: [ 7 | new BundleAnalyzerPlugin() 8 | ] 9 | }); 10 | -------------------------------------------------------------------------------- /webpack/build-utils.js: -------------------------------------------------------------------------------- 1 | const isMin = !!process.env.WEBPACK_BUILD_MIN; 2 | 3 | const minFilenamePart = isMin ? '.min' : ''; 4 | 5 | module.exports = { isMin, minFilenamePart }; 6 | -------------------------------------------------------------------------------- /webpack/build.config.js: -------------------------------------------------------------------------------- 1 | const { merge } = require('webpack-merge'); 2 | const webpackCommon = require('./common.config'); 3 | const TerserPlugin = require('terser-webpack-plugin'); 4 | const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); 5 | const { isMin } = require('./build-utils'); 6 | 7 | module.exports = merge(webpackCommon, { 8 | mode: 'production', 9 | 10 | optimization: isMin ? { 11 | minimize: true, 12 | minimizer: [ 13 | new CssMinimizerPlugin(), 14 | new TerserPlugin() 15 | ] 16 | } : undefined 17 | }); 18 | -------------------------------------------------------------------------------- /webpack/copy-light-bundle.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | const src = path.resolve(__dirname, '../dist/cld-video-player.js'); 5 | const dest = path.resolve(__dirname, '../dist/cld-video-player.light.js'); 6 | const srcMin = path.resolve(__dirname, '../dist/cld-video-player.min.js'); 7 | const destMin = path.resolve(__dirname, '../dist/cld-video-player.light.min.js'); 8 | const warning = 'console.warn(\'[Cloudinary] The "light" video-player is deprecated and will be removed in a future release. The main player is now light by default. Please use that instead.\');\n'; 9 | 10 | fs.readFile(src, 'utf8', (err, data) => { 11 | if (err) throw err; 12 | fs.writeFile(dest, warning + data, 'utf8', (err) => { 13 | if (err) throw err; 14 | console.log('Light bundle created with deprecation warning.'); 15 | }); 16 | }); 17 | 18 | fs.readFile(srcMin, 'utf8', (err, data) => { 19 | if (err) throw err; 20 | fs.writeFile(destMin, warning + data, 'utf8', (err) => { 21 | if (err) throw err; 22 | console.log('Light minified bundle created with deprecation warning.'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /webpack/dev.config.js: -------------------------------------------------------------------------------- 1 | const { merge } = require('webpack-merge'); 2 | const webpackCommon = require('./common.config'); 3 | const path = require('path'); 4 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 5 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 6 | 7 | const env = require('../env'); 8 | 9 | module.exports = merge(webpackCommon, { 10 | mode: 'development', 11 | 12 | plugins: [ 13 | new HtmlWebpackPlugin({ 14 | inject: false, 15 | template: path.resolve(__dirname, '../docs/index.html') 16 | }), 17 | new CopyWebpackPlugin({ 18 | patterns: [{ 19 | from: path.resolve(__dirname, '../docs'), 20 | globOptions: { 21 | ignore: [ 22 | '**/node_modules', 23 | ], 24 | }, 25 | }], 26 | }) 27 | ], 28 | 29 | devServer: { 30 | port: env.devServer.port || 3000, 31 | open: ['index.html'], 32 | headers: { 33 | 'Access-Control-Allow-Origin': '*', 34 | 'Access-Control-Allow-Headers': '*' 35 | } 36 | } 37 | 38 | }); 39 | -------------------------------------------------------------------------------- /webpack/es6.config.js: -------------------------------------------------------------------------------- 1 | const { merge } = require('webpack-merge'); 2 | const webpackCommon = require('./common.config'); 3 | const path = require('path'); 4 | 5 | delete webpackCommon.output; // overwrite 6 | 7 | const outputPath = path.resolve(__dirname, '../lib'); 8 | 9 | module.exports = merge(webpackCommon, { 10 | mode: 'production', 11 | 12 | entry: { 13 | 'cld-video-player': './index.es.js', // default 14 | 'videoPlayer': './index.videoPlayer.js', 15 | 'player': './index.player.js', 16 | 'all': './index.all.js' 17 | }, 18 | 19 | output: { 20 | filename: '[name].js', 21 | path: outputPath, 22 | chunkFilename: '[name].js', 23 | publicPath: '', 24 | library: { 25 | type: 'module' 26 | }, 27 | chunkLoadingGlobal: 'cloudinaryVideoPlayerChunkLoading' 28 | }, 29 | 30 | experiments: { 31 | outputModule: true 32 | } 33 | }); 34 | -------------------------------------------------------------------------------- /webpack/puppeteer.config.js: -------------------------------------------------------------------------------- 1 | const { merge } = require('webpack-merge'); 2 | const devConf = require('./dev.config'); 3 | 4 | module.exports = merge(devConf, { 5 | devServer: { 6 | open: false 7 | } 8 | }); 9 | --------------------------------------------------------------------------------