├── .editorconfig
├── .gitignore
├── .npmignore
├── .nvmrc
├── .travis.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── index.html
├── package-lock.json
├── package.json
├── scripts
├── jsdoc.config.json
├── karma.conf.js
└── rollup.config.js
├── src
└── index.js
├── test
└── index.test.js
└── vendor
└── 1
└── default_default
├── index.html
├── index.js
└── index.min.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | end_of_line = lf
7 | indent_style = space
8 | indent_size = 2
9 | insert_final_newline = true
10 | trim_trailing_whitespace = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OS
2 | Thumbs.db
3 | ehthumbs.db
4 | Desktop.ini
5 | .DS_Store
6 | ._*
7 |
8 | # Editors
9 | *~
10 | *.swp
11 | *.tmproj
12 | *.tmproject
13 | *.sublime-*
14 | .idea/
15 | .project/
16 | .settings/
17 | .vscode/
18 |
19 | # Logs
20 | logs
21 | *.log
22 | npm-debug.log*
23 |
24 | # Dependency directories
25 | bower_components/
26 | node_modules/
27 |
28 | # Build-related directories
29 | dist/
30 | docs/api/
31 | test/dist/
32 | .eslintcache
33 | .yo-rc.json
34 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # Intentionally left blank, so that npm does not ignore anything by default,
2 | # but relies on the package.json "files" array to explicitly define what ends
3 | # up in the package.
4 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | lts/*
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | dist: trusty
3 | language: node_js
4 | # node version is specified using the .nvmrc file
5 | before_install:
6 | - npm install -g greenkeeper-lockfile@1
7 | before_script:
8 | - export DISPLAY=:99.0
9 | - sh -e /etc/init.d/xvfb start
10 | - greenkeeper-lockfile-update
11 | after_script:
12 | - greenkeeper-lockfile-upload
13 | addons:
14 | firefox: latest
15 | chrome: stable
16 |
17 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
2 | # [1.5.0](https://github.com/brightcove/react-player-loader/compare/v1.4.2...v1.5.0) (2024-02-05)
3 |
4 | ### Features
5 |
6 | * support strict mode ([#114](https://github.com/brightcove/react-player-loader/issues/114)) ([846d7a1](https://github.com/brightcove/react-player-loader/commit/846d7a1))
7 |
8 | ### Bug Fixes
9 |
10 | * disable firefox from running in karma ([#121](https://github.com/brightcove/react-player-loader/issues/121)) ([ea097d0](https://github.com/brightcove/react-player-loader/commit/ea097d0))
11 |
12 |
13 | ## [1.4.2](https://github.com/brightcove/react-player-loader/compare/v1.4.1...v1.4.2) (2022-07-19)
14 |
15 | ### Bug Fixes
16 |
17 | * Fix an issue where the updatePlayer method would throw when using iframe embeds ([#96](https://github.com/brightcove/react-player-loader/issues/96)) ([a184999](https://github.com/brightcove/react-player-loader/commit/a184999))
18 |
19 | ### Chores
20 |
21 | * update jsdoc ([#95](https://github.com/brightcove/react-player-loader/issues/95)) ([2424025](https://github.com/brightcove/react-player-loader/commit/2424025))
22 |
23 |
24 | ## [1.4.1](https://github.com/brightcove/react-player-loader/compare/v1.4.0...v1.4.1) (2020-10-09)
25 |
26 | ### Chores
27 |
28 | * **package:** Update [@brightcove](https://github.com/brightcove)/player-loader to 1.8.0 ([48c180b](https://github.com/brightcove/react-player-loader/commit/48c180b))
29 |
30 |
31 | # [1.4.0](https://github.com/brightcove/react-player-loader/compare/v1.3.0...v1.4.0) (2020-01-31)
32 |
33 | ### Features
34 |
35 | * add support for manualReloadFromPropChanges prop ([#67](https://github.com/brightcove/react-player-loader/issues/67)) ([756af85](https://github.com/brightcove/react-player-loader/commit/756af85))
36 |
37 |
38 | # [1.3.0](https://github.com/brightcove/react-player-loader/compare/v1.2.1...v1.3.0) (2019-07-22)
39 |
40 | ### Features
41 |
42 | * support changes to props ([#44](https://github.com/brightcove/react-player-loader/issues/44)) ([f742a2d](https://github.com/brightcove/react-player-loader/commit/f742a2d))
43 |
44 |
45 | ## [1.2.1](https://github.com/brightcove/react-player-loader/compare/v1.2.0...v1.2.1) (2019-07-08)
46 |
47 | ### Bug Fixes
48 |
49 | * Fix missing dependency on [@brightcove](https://github.com/brightcove)/player-loader ([#42](https://github.com/brightcove/react-player-loader/issues/42)) ([af63590](https://github.com/brightcove/react-player-loader/commit/af63590))
50 |
51 |
52 | # [1.2.0](https://github.com/brightcove/react-player-loader/compare/v1.1.2...v1.2.0) (2018-12-13)
53 |
54 | ### Features
55 |
56 | * Add support for attrs prop to customize component element ([#29](https://github.com/brightcove/react-player-loader/issues/29)) ([b1abf92](https://github.com/brightcove/react-player-loader/commit/b1abf92))
57 |
58 | ### Chores
59 |
60 | * **package:** update dependencies ([#28](https://github.com/brightcove/react-player-loader/issues/28)) ([5250042](https://github.com/brightcove/react-player-loader/commit/5250042))
61 |
62 |
63 | ## [1.1.2](https://github.com/brightcove/react-player-loader/compare/v1.1.1...v1.1.2) (2018-10-05)
64 |
65 | ### Chores
66 |
67 | * **package:** update all package versions ([ef58cde](https://github.com/brightcove/react-player-loader/commit/ef58cde))
68 | * **package:** update videojs-standard to version 8.0.2 (#20) ([5d753b1](https://github.com/brightcove/react-player-loader/commit/5d753b1)), closes [#20](https://github.com/brightcove/react-player-loader/issues/20)
69 |
70 |
71 | ## [1.1.1](https://github.com/brightcove/react-player-loader/compare/v1.1.0...v1.1.1) (2018-09-17)
72 |
73 | ### Chores
74 |
75 | * Mark players as having been loaded with this library. (#18) ([8396842](https://github.com/brightcove/react-player-loader/commit/8396842)), closes [#18](https://github.com/brightcove/react-player-loader/issues/18)
76 |
77 |
78 | # [1.1.0](https://github.com/brightcove/react-player-loader/compare/v1.0.4...v1.1.0) (2018-09-12)
79 |
80 | ### Features
81 |
82 | * Use ref callback to add support for React 15 (#15) ([2c0161b](https://github.com/brightcove/react-player-loader/commit/2c0161b)), closes [#15](https://github.com/brightcove/react-player-loader/issues/15)
83 |
84 | ### Chores
85 |
86 | * **package:** Update [@brightcove](https://github.com/brightcove)/player-loader to 1.4.1 (#16) ([7fb309d](https://github.com/brightcove/react-player-loader/commit/7fb309d)), closes [#16](https://github.com/brightcove/react-player-loader/issues/16)
87 | * **package:** update videojs-generate-rollup-config to version 2.2.0 (#13) ([abcbe8a](https://github.com/brightcove/react-player-loader/commit/abcbe8a)), closes [#13](https://github.com/brightcove/react-player-loader/issues/13)
88 |
89 |
90 | ## [1.0.4](https://github.com/brightcove/react-player-loader/compare/v1.0.3...v1.0.4) (2018-09-05)
91 |
92 | ### Chores
93 |
94 | * **package:** Update [@brightcove](https://github.com/brightcove)/player-loader to fix install issues (#12) ([b19d5cf](https://github.com/brightcove/react-player-loader/commit/b19d5cf)), closes [#12](https://github.com/brightcove/react-player-loader/issues/12)
95 |
96 |
97 | ## [1.0.3](https://github.com/brightcove/react-player-loader/compare/v1.0.2...v1.0.3) (2018-09-05)
98 |
99 | ### Bug Fixes
100 |
101 | * Remove the postinstall script to prevent install issues (#11) ([c9f4b50](https://github.com/brightcove/react-player-loader/commit/c9f4b50)), closes [#11](https://github.com/brightcove/react-player-loader/issues/11)
102 |
103 |
104 | ## [1.0.2](https://github.com/brightcove/react-player-loader/compare/v1.0.1...v1.0.2) (2018-09-04)
105 |
106 | ### Bug Fixes
107 |
108 | * Fix an issue where the dist files for this package included some unexpected ES6 code. (#9) ([e6cb690](https://github.com/brightcove/react-player-loader/commit/e6cb690)), closes [#9](https://github.com/brightcove/react-player-loader/issues/9)
109 |
110 | ### Chores
111 |
112 | * **package:** Update dependencies to enable Greenkeeper 🌴 (#6) ([494e94e](https://github.com/brightcove/react-player-loader/commit/494e94e)), closes [#6](https://github.com/brightcove/react-player-loader/issues/6)
113 |
114 |
115 | ## [1.0.1](https://github.com/brightcove/react-player-loader/compare/v1.0.0...v1.0.1) (2018-08-30)
116 |
117 | ### Bug Fixes
118 |
119 | * Do not bundle React with the module builds and update tooling using the plugin generator v7.2.0 (#5) ([904885a](https://github.com/brightcove/react-player-loader/commit/904885a)), closes [#5](https://github.com/brightcove/react-player-loader/issues/5)
120 |
121 |
122 | # [1.0.0](https://github.com/brightcove/react-player-loader/compare/v0.2.0...v1.0.0) (2018-08-28)
123 |
124 | ### Documentation
125 |
126 | * Update README to clarify Brightcove Player version support. (#3) ([b98d120](https://github.com/brightcove/react-player-loader/commit/b98d120)), closes [#3](https://github.com/brightcove/react-player-loader/issues/3)
127 |
128 |
129 | # [0.2.0](https://github.com/brightcove/react-brightcove-player/compare/v0.1.0...v0.2.0) (2018-08-28)
130 |
131 | ### Features
132 |
133 | * Rename to [@brightcove](https://github.com/brightcove) scoped package. (#2) ([ded1597](https://github.com/brightcove/react-brightcove-player/commit/ded1597)), closes [#2](https://github.com/brightcove/react-brightcove-player/issues/2)
134 |
135 |
136 | # 0.1.0 (2018-08-20)
137 |
138 | ### Features
139 |
140 | * initial implementation (#1) ([ea8f47d](https://github.com/brightcove/react-brightcove-player/commit/ea8f47d)), closes [#1](https://github.com/brightcove/react-brightcove-player/issues/1)
141 |
142 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # CONTRIBUTING
2 |
3 | We welcome contributions from everyone!
4 |
5 | ## Getting Started
6 |
7 | Make sure you have Node.js 4.8 or higher and npm installed.
8 |
9 | 1. Fork this repository and clone your fork
10 | 1. Install dependencies: `npm install`
11 | 1. Run a development server: `npm start`
12 |
13 | ### Making Changes
14 |
15 | Refer to the [video.js plugin conventions][conventions] for more detail on best practices and tooling for video.js plugin authorship.
16 |
17 | When you've made your changes, push your commit(s) to your fork and issue a pull request against the original repository.
18 |
19 | ### Running Tests
20 |
21 | Testing is a crucial part of any software project. For all but the most trivial changes (typos, etc) test cases are expected. Tests are run in actual browsers using [Karma][karma].
22 |
23 | - In all available and supported browsers: `npm test`
24 | - In a specific browser: `npm run test:chrome`, `npm run test:firefox`, etc.
25 | - While development server is running (`npm start`), navigate to [`http://localhost:9999/test/`][local]
26 |
27 |
28 | [karma]: http://karma-runner.github.io/
29 | [local]: http://localhost:9999/test/
30 | [conventions]: https://github.com/videojs/generator-videojs-plugin/blob/master/docs/conventions.md
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright Brightcove, Inc.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # @brightcove/react-player-loader
2 |
3 | [](https://travis-ci.org/brightcove/react-player-loader)
4 | [](https://greenkeeper.io/)
5 |
6 | [](https://npmjs.com/package/@brightcove/react-player-loader)
7 |
8 | A React component to load a Brightcove Player in the browser.
9 |
10 | ## Brightcove Player Support
11 |
12 | This library has [the same support characteristics as the Brightcove Player Loader](https://github.com/brightcove/player-loader#brightcove-player-support).
13 |
14 | ## Table of Contents
15 |
16 |
17 |
18 |
19 |
20 | - [Installation](#installation)
21 | - [Standard Usage with JSX](#standard-usage-with-jsx)
22 | - [Props](#props)
23 | - [`attrs`](#attrs)
24 | - [`baseUrl`](#baseurl)
25 | - [`manualReloadFromPropChanges`](#manualreloadfrompropchanges)
26 | - [Other Props](#other-props)
27 | - [Effects of Prop Changes](#effects-of-prop-changes)
28 | - [View the Demo](#view-the-demo)
29 | - [Alternate Usage](#alternate-usage)
30 | - [ES Module (without JSX)](#es-module-without-jsx)
31 | - [CommonJS](#commonjs)
32 | - [`
204 |
205 |
206 |
222 | ```
223 |
224 | [react]: https://www.npmjs.com/package/react
225 | [react-dom]: https://www.npmjs.com/package/react-dom
226 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Nothing will appear below because this page uses an invalid account ID by default.
8 | Open the console and use window.customApp.setState({})
to try changing props.
9 |
10 |
11 |
12 |
13 |
14 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@brightcove/react-player-loader",
3 | "version": "1.5.0",
4 | "description": "The official react component for the Brightcove Player",
5 | "main": "dist/brightcove-react-player-loader.cjs.js",
6 | "module": "dist/brightcove-react-player-loader.es.js",
7 | "repository": {
8 | "type": "git",
9 | "url": "https://github.com/brightcove/react-player-loader.git"
10 | },
11 | "generator-videojs-plugin": {
12 | "version": "7.4.0"
13 | },
14 | "browserslist": [
15 | "defaults",
16 | "ie 11"
17 | ],
18 | "keywords": [
19 | "audio",
20 | "brightcove",
21 | "media",
22 | "player",
23 | "react",
24 | "react-component",
25 | "video"
26 | ],
27 | "scripts": {
28 | "prebuild": "npm run clean",
29 | "build": "npm-run-all -p build:*",
30 | "build:js": "rollup -c scripts/rollup.config.js",
31 | "clean": "shx rm -rf ./dist ./test/dist",
32 | "postclean": "shx mkdir -p ./dist ./test/dist",
33 | "docs": "npm-run-all docs:*",
34 | "docs:api": "jsdoc src -c scripts/jsdoc.config.json -r -d docs/api",
35 | "docs:toc": "doctoc README.md",
36 | "lint": "vjsstandard",
37 | "server": "karma start scripts/karma.conf.js --singleRun=false --auto-watch",
38 | "start": "npm-run-all -p server watch",
39 | "pretest": "npm-run-all lint build",
40 | "test": "npm-run-all test:*",
41 | "posttest": "shx cat test/dist/coverage/text.txt",
42 | "test:unit": "karma start scripts/karma.conf.js",
43 | "test:verify": "vjsverify --verbose",
44 | "update-changelog": "conventional-changelog -p videojs -i CHANGELOG.md -s",
45 | "version": "is-prerelease || npm run update-changelog && git add CHANGELOG.md",
46 | "watch": "npm-run-all -p watch:*",
47 | "watch:js": "npm run build:js -- -w",
48 | "prepublishOnly": "npm-run-all build test:verify"
49 | },
50 | "author": "Brightcove, Inc.",
51 | "license": "Apache-2.0",
52 | "vjsstandard": {
53 | "ignore": [
54 | "dist",
55 | "docs",
56 | "test/dist",
57 | "vendor"
58 | ]
59 | },
60 | "files": [
61 | "CONTRIBUTING.md",
62 | "dist/",
63 | "docs/",
64 | "index.html",
65 | "scripts/",
66 | "src/",
67 | "test/"
68 | ],
69 | "dependencies": {
70 | "@brightcove/player-loader": "^1.8.0"
71 | },
72 | "devDependencies": {
73 | "@testing-library/react": "^8.0.1",
74 | "conventional-changelog-cli": "^2.0.21",
75 | "conventional-changelog-videojs": "^3.0.0",
76 | "create-react-class": "^15.6.3",
77 | "doctoc": "^1.4.0",
78 | "husky": "^1.3.1",
79 | "in-publish": "^2.0.0",
80 | "jsdoc": "^3.6.10",
81 | "karma": "^4.1.0",
82 | "lint-staged": "^8.2.1",
83 | "not-prerelease": "^1.0.1",
84 | "npm-merge-driver-install": "^1.1.1",
85 | "npm-run-all": "^4.1.5",
86 | "pkg-ok": "^2.3.1",
87 | "react": "^18.2.0",
88 | "react-dom": "^18.2.0",
89 | "rollup": "^1.16.2",
90 | "shx": "^0.3.2",
91 | "sinon": "^7.3.2",
92 | "videojs-generate-karma-config": "^5.3.0",
93 | "videojs-generate-rollup-config": "^3.2.0",
94 | "videojs-generator-verify": "~1.2.0",
95 | "videojs-standard": "^8.0.3"
96 | },
97 | "peerDependencies": {
98 | "react": ">=15.0.0"
99 | },
100 | "husky": {
101 | "hooks": {
102 | "pre-commit": "lint-staged"
103 | }
104 | },
105 | "lint-staged": {
106 | "*.js": [
107 | "vjsstandard --fix",
108 | "git add"
109 | ],
110 | "README.md": [
111 | "npm run docs:toc",
112 | "git add"
113 | ]
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/scripts/jsdoc.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": ["plugins/markdown"]
3 | }
4 |
--------------------------------------------------------------------------------
/scripts/karma.conf.js:
--------------------------------------------------------------------------------
1 | const generate = require('videojs-generate-karma-config');
2 |
3 | module.exports = function(config) {
4 |
5 | // see https://github.com/videojs/videojs-generate-karma-config
6 | // for options
7 | const options = {
8 | // TODO - currently firefox headless fails to run with karma, blocking the npm version script.
9 | // We should look into a better workaround that allows us to still run firefox through karma
10 | browsers(aboutToRun) {
11 | return aboutToRun.filter(launcherName => launcherName !== 'FirefoxHeadless');
12 | },
13 | files(defaults) {
14 | // defaults don't work for this project
15 | return [
16 | 'node_modules/sinon/pkg/sinon.js',
17 | 'node_modules/react/umd/react.development.js',
18 | 'node_modules/react-dom/umd/react-dom.development.js',
19 | 'test/dist/bundle.js'
20 | ];
21 | }
22 | };
23 |
24 | config = generate(config, options);
25 |
26 | // any other custom stuff not supported by options here!
27 | };
28 |
29 |
--------------------------------------------------------------------------------
/scripts/rollup.config.js:
--------------------------------------------------------------------------------
1 | const generate = require('videojs-generate-rollup-config');
2 |
3 | // see https://github.com/videojs/videojs-generate-rollup-config
4 | // for options
5 | const options = {
6 | input: 'src/index.js',
7 | distName: 'brightcove-react-player-loader',
8 | exportName: 'BrightcoveReactPlayerLoader',
9 | externals(defaults) {
10 | return {
11 | browser: defaults.browser.concat([
12 | 'react'
13 | ]),
14 | module: defaults.module.concat([
15 | 'react'
16 | ]),
17 | test: defaults.test.concat([
18 | 'react',
19 | 'react-dom'
20 | ])
21 | };
22 | },
23 | globals(defaults) {
24 | return {
25 | browser: Object.assign(defaults.browser, {
26 | react: 'React'
27 | }),
28 | module: defaults.module,
29 | test: Object.assign(defaults.test, {
30 |
31 | // This is a deep dependency of @testing-library/react that doesn't
32 | // play nice with Rollup...
33 | '@sheerun/mutationobserver-shim': 'MutationObserver',
34 | 'react': 'React',
35 | 'react-dom': 'ReactDOM'
36 | })
37 | };
38 | }
39 | };
40 |
41 | const config = generate(options);
42 |
43 | // Add additonal builds/customization here!
44 |
45 | // export the builds to rollup
46 | export default Object.values(config.builds);
47 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import playerLoader from '@brightcove/player-loader';
3 |
4 | /**
5 | * These prop changes can be handled by an internal player state change rather
6 | * than a full dispose/recreate.
7 | *
8 | * @private
9 | * @type {Object}
10 | */
11 | const UPDATEABLE_PROPS = [
12 | 'catalogSearch',
13 | 'catalogSequence',
14 | 'playlistId',
15 | 'playlistVideoId',
16 | 'videoId'
17 | ];
18 |
19 | const logError = (err) => {
20 | /* eslint-disable no-console */
21 | if (err && console && console.error) {
22 | console.error(err);
23 | }
24 | /* eslint-enable no-console */
25 | };
26 |
27 | /**
28 | * The official React component for the Brightcove Player!
29 | *
30 | * This uses `@brightcove/player-loader` to load a player into a React
31 | * component based on the given props.
32 | */
33 | class ReactPlayerLoader extends React.Component {
34 |
35 | /**
36 | * Create a new Brightcove player.
37 | *
38 | * @param {Object} props
39 | * Most options will be passed along to player-loader, except for
40 | * options that are listed. See README.md for more detail.
41 | *
42 | * @param {string} [props.baseUrl]
43 | * The base URL to use when requesting a player
44 | *
45 | * @param {Object} [props.attrs]
46 | * Used to set attributes on the component element that contains the
47 | * embedded Brightcove Player.
48 | *
49 | * @param {boolean} [props.manualReloadFromPropChanges]
50 | * Used to specify if reloading the player after prop changes will be handled manually.
51 | *
52 | */
53 | constructor(props) {
54 | super(props);
55 | this.refNode = null;
56 | this.setRefNode = ref => {
57 | this.refNode = ref;
58 | };
59 | this.loadPlayer = this.loadPlayer.bind(this);
60 | }
61 |
62 | /**
63 | * Loads a new player based on the current props.
64 | */
65 | loadPlayer() {
66 | // Guard against loading the player twice, which would be caused by React's
67 | // strict mode unmounting and immediately re-mounting the component
68 | if (!this.loading_) {
69 | this.loading_ = true;
70 | } else {
71 | // eslint-disable-next-line no-console
72 | console.log('Brightcove React Player Loader aborted a subsequent player load while a load was pending');
73 | return;
74 | }
75 |
76 | // If there is any player currently loaded, dispose it before fetching a
77 | // new one.
78 | this.disposePlayer();
79 |
80 | // We need to provide our own callbacks below, so we cache these
81 | // user-provided callbacks for use later.
82 | const userSuccess = this.props.onSuccess;
83 | const userFailure = this.props.onFailure;
84 |
85 | const options = Object.assign({}, this.props, {
86 | refNode: this.refNode,
87 | refNodeInsert: 'append',
88 | onSuccess: ({ref, type}) => {
89 | // The player load process has completed. A subsequent load,
90 | // e.g. because of a props change, should be allowed
91 | this.loading_ = false;
92 |
93 | // If the component is not mounted when the callback fires, dispose
94 | // the player and bail out.
95 | if (!this.isMounted_) {
96 | this.disposePlayer(ref);
97 | return;
98 | }
99 |
100 | // Store a player reference on the component.
101 | this.player = ref;
102 |
103 | // Null out the player reference when the player is disposed from
104 | // outside the component.
105 | if (type === 'in-page') {
106 | ref.one('dispose', () => {
107 | this.player = null;
108 | });
109 | }
110 |
111 | // Add a REACT_PLAYER_LOADER property to bcinfo to indicate this player
112 | // was loaded via that mechanism.
113 | if (ref.bcinfo) {
114 | ref.bcinfo.REACT_PLAYER_LOADER = true;
115 | }
116 |
117 | // Call a user-provided onSuccess callback.
118 | if (typeof userSuccess === 'function') {
119 | userSuccess({ref, type});
120 | }
121 | },
122 | onFailure: (error) => {
123 | // The player load process has completed. A subsequent load,
124 | // e.g. because of a props change, should be allowed
125 | this.loading_ = false;
126 |
127 | // Ignore errors when not mounted.
128 | if (!this.isMounted_) {
129 | return;
130 | }
131 |
132 | // Call a user-provided onFailure callback.
133 | if (typeof userFailure === 'function') {
134 | userFailure(error);
135 | return;
136 | }
137 |
138 | // Fall back to throwing an error;
139 | throw new Error(error);
140 | }
141 | });
142 |
143 | // Delete props that are not meant to be passed to player-loader.
144 | delete options.attrs;
145 | delete options.baseUrl;
146 | delete options.manualReloadFromPropChanges;
147 |
148 | // If a base URL is provided, it should only apply to this player load.
149 | // This means we need to back up the original base URL and restore it
150 | // _after_ we call player loader.
151 | const originalBaseUrl = playerLoader.getBaseUrl();
152 |
153 | if (this.props.baseUrl) {
154 | playerLoader.setBaseUrl(this.props.baseUrl);
155 | }
156 |
157 | playerLoader(options);
158 | playerLoader.setBaseUrl(originalBaseUrl);
159 | }
160 |
161 | /**
162 | * Disposes the current player, if there is one.
163 | */
164 | disposePlayer() {
165 |
166 | // Nothing to dispose.
167 | if (!this.player) {
168 | return;
169 | }
170 |
171 | // Dispose an in-page player.
172 | if (this.player.dispose) {
173 | this.player.dispose();
174 |
175 | // Dispose an iframe player.
176 | } else if (this.player.parentNode) {
177 | this.player.parentNode.removeChild(this.player);
178 | }
179 |
180 | // Null out the player reference.
181 | this.player = null;
182 | }
183 |
184 | /**
185 | * Find the index of the `playlistVideoId` prop within the player's playlist.
186 | *
187 | * @param {Object[]} playlist
188 | * An array of playlist item objects.
189 | *
190 | * @return {number}
191 | * The index of the `playlistVideoId` or `-1` if the player has been
192 | * disposed, is not using the playlist plugin, or if not found.
193 | */
194 | findPlaylistVideoIdIndex_(playlist) {
195 | const {playlistVideoId} = this.props;
196 |
197 | if (Array.isArray(playlist) && playlistVideoId) {
198 | for (let i = 0; i < playlist.length; i++) {
199 | const {id, referenceId} = playlist[i];
200 |
201 | if (id === playlistVideoId || `ref:${referenceId}` === playlistVideoId) {
202 | return i;
203 | }
204 | }
205 | }
206 |
207 | return -1;
208 | }
209 |
210 | /**
211 | * Create a Playback API callback function for the component's player.
212 | *
213 | * @private
214 | * @param {string} requestType
215 | * The Playback API request type (e.g. "video" or "playlist").
216 | *
217 | * @param {Object} changes
218 | * An object. The keys of this object are the props that changed.
219 | *
220 | * @return {Function}
221 | * A callback for the Playback API request.
222 | */
223 | createPlaybackAPICallback_(requestType, changes) {
224 | return (err, data) => {
225 | if (err) {
226 | logError(err);
227 | return;
228 | }
229 |
230 | // If the playlistVideoId changed and this is a playlist request, we
231 | // need to search through the playlist items to find the correct
232 | // starting index.
233 | if (requestType === 'playlist' && changes.playlistVideoId) {
234 | const i = this.findPlaylistVideoIdIndex_(data);
235 |
236 | if (i > -1) {
237 | data.startingIndex = i;
238 | }
239 | }
240 |
241 | this.player.catalog.load(data);
242 | };
243 | }
244 |
245 | /**
246 | * Update the player based on changes to certain props that do not require
247 | * a full player dispose/recreate.
248 | *
249 | * @param {Object} changes
250 | * An object. The keys of this object are the props that changed.
251 | */
252 | updatePlayer(changes) {
253 |
254 | // No player exists, player is disposed, or not using the catalog
255 | if (!this.player || !this.player.el || !this.player.el()) {
256 | return;
257 | }
258 |
259 | // If the player is using the catalog plugin, we _may_ populate this
260 | // variable with an object.
261 | let catalogParams;
262 |
263 | if (this.player.usingPlugin('catalog')) {
264 |
265 | // There is a new catalog sequence request. This takes precedence over
266 | // other catalog updates because it is a different call.
267 | if (changes.catalogSequence && this.props.catalogSequence) {
268 | const callback = this.createPlaybackAPICallback_('sequence', changes);
269 |
270 | this.player.catalog.getLazySequence(this.props.catalogSequence, callback, this.props.adConfigId);
271 | return;
272 | }
273 |
274 | if (changes.videoId && this.props.videoId) {
275 | catalogParams = {
276 | type: 'video',
277 | id: this.props.videoId
278 | };
279 | } else if (changes.playlistId && this.props.playlistId) {
280 | catalogParams = {
281 | type: 'playlist',
282 | id: this.props.playlistId
283 | };
284 | } else if (changes.catalogSearch && this.props.catalogSearch) {
285 | catalogParams = {
286 | type: 'search',
287 | q: this.props.catalogSearch
288 | };
289 | }
290 | }
291 |
292 | // If `catalogParams` is `undefined` here, that means the player either
293 | // does not have the catalog plugin or no valid catalog request can be made.
294 | if (catalogParams) {
295 | if (this.props.adConfigId) {
296 | catalogParams.adConfigId = this.props.adConfigId;
297 | }
298 |
299 | if (this.props.deliveryConfigId) {
300 | catalogParams.deliveryConfigId = this.props.deliveryConfigId;
301 | }
302 |
303 | // We use the callback style here to make tests simpler in IE11 (no need
304 | // for a Promise polyfill).
305 | const callback = this.createPlaybackAPICallback_(catalogParams.type, changes);
306 |
307 | this.player.catalog.get(catalogParams, callback);
308 |
309 | // If no catalog request is being made, we may still need to update the
310 | // playlist selected video.
311 | } else if (
312 | changes.playlistVideoId &&
313 | this.props.playlistVideoId &&
314 | this.player.usingPlugin('playlist')
315 | ) {
316 | const i = this.findPlaylistVideoIdIndex_(this.player.playlist());
317 |
318 | if (i > -1) {
319 | this.player.playlist.currentItem(i);
320 | }
321 | }
322 | }
323 |
324 | /**
325 | * Called just after the component has mounted.
326 | */
327 | componentDidMount() {
328 | this.isMounted_ = true;
329 | this.loadPlayer();
330 | }
331 |
332 | /**
333 | * Called when the component props are updated.
334 | *
335 | * Some prop changes may trigger special behavior (see `propChangeHandlers`),
336 | * but if ANY prop is changed that is NOT handled, the player will be
337 | * disposed/recreated entirely.
338 | *
339 | * @param {Object} prevProps
340 | * The previous props state before change.
341 | */
342 | componentDidUpdate(prevProps) {
343 |
344 | // Calculate the prop changes.
345 | const changes = Object.keys(prevProps).reduce((acc, key) => {
346 | const previous = prevProps[key];
347 | const current = this.props[key];
348 |
349 | // Do not compare functions
350 | if (typeof current === 'function') {
351 | return acc;
352 | }
353 |
354 | if (typeof current === 'object' && current !== null) {
355 | if (JSON.stringify(current) !== JSON.stringify(previous)) {
356 | acc[key] = true;
357 | }
358 |
359 | return acc;
360 | }
361 |
362 | if (current !== previous) {
363 | acc[key] = true;
364 | }
365 |
366 | return acc;
367 | }, {});
368 |
369 | if (!this.props.manualReloadFromPropChanges) {
370 | // Dispose and recreate the player if any changed keys cannot be handled.
371 | if (Object.keys(changes).some(k => UPDATEABLE_PROPS.indexOf(k) === -1)) {
372 | this.loadPlayer();
373 | return;
374 | }
375 | }
376 |
377 | this.updatePlayer(changes);
378 | }
379 |
380 | /**
381 | * Called just before a component unmounts. Disposes the player.
382 | */
383 | componentWillUnmount() {
384 | this.isMounted_ = false;
385 | this.disposePlayer();
386 | }
387 |
388 | /**
389 | * Renders the component.
390 | *
391 | * @return {ReactElement}
392 | * The react element to render.
393 | */
394 | render() {
395 | const props = Object.assign(
396 | {className: 'brightcove-react-player-loader'},
397 | this.props.attrs,
398 | {ref: this.setRefNode}
399 | );
400 |
401 | return React.createElement('div', props);
402 | }
403 | }
404 |
405 | export default ReactPlayerLoader;
406 |
--------------------------------------------------------------------------------
/test/index.test.js:
--------------------------------------------------------------------------------
1 | import document from 'global/document';
2 | import window from 'global/window';
3 | import QUnit from 'qunit';
4 | import sinon from 'sinon';
5 | import React from 'react';
6 | import ReactDOM from 'react-dom';
7 | import {render, cleanup} from '@testing-library/react';
8 | import ReactPlayerLoader from '../src/index.js';
9 | import BrightcovePlayerLoader from '@brightcove/player-loader';
10 |
11 | QUnit.module('ReactPlayerLoader', {
12 | beforeEach() {
13 | this.fixture = document.getElementById('qunit-fixture');
14 | this.originalBaseUrl = BrightcovePlayerLoader.getBaseUrl();
15 | BrightcovePlayerLoader.setBaseUrl(`${window.location.origin}/vendor/`);
16 | },
17 | afterEach() {
18 | cleanup();
19 |
20 | // reset the base url and global state
21 | BrightcovePlayerLoader.setBaseUrl(this.originalBaseUrl);
22 | BrightcovePlayerLoader.reset();
23 |
24 | // unmount all components on the fixture
25 | ReactDOM.unmountComponentAtNode(this.fixture);
26 | }
27 | }, function() {
28 |
29 | QUnit.module('baseline usage');
30 |
31 | QUnit.test('failure', function(assert) {
32 | const done = assert.async();
33 |
34 | assert.expect(2);
35 |
36 | const reactPlayerLoader = ReactDOM.render(
37 | React.createElement(ReactPlayerLoader, {
38 | accountId: '2',
39 | onFailure: (failure) => {
40 | assert.ok(failure, 'failed to download non-existent player');
41 | done();
42 | }
43 | }),
44 | this.fixture
45 | );
46 |
47 | assert.ok(reactPlayerLoader, 'player loader react component created');
48 | });
49 |
50 | QUnit.test('success', function(assert) {
51 | const done = assert.async();
52 |
53 | assert.expect(2);
54 |
55 | const reactPlayerLoader = ReactDOM.render(
56 | React.createElement(ReactPlayerLoader, {
57 | accountId: '1',
58 | onSuccess: ({ref, type}) => {
59 | assert.ok(ref, 'downloaded and created a player');
60 | done();
61 | }
62 | }),
63 | this.fixture
64 | );
65 |
66 | assert.ok(reactPlayerLoader, 'player loader react component created');
67 | });
68 |
69 | QUnit.test('unmount after success', function(assert) {
70 | const done = assert.async();
71 |
72 | assert.expect(2);
73 |
74 | const reactPlayerLoader = ReactDOM.render(
75 | React.createElement(ReactPlayerLoader, {
76 | accountId: '1',
77 | onSuccess: ({ref, type}) => {
78 | assert.ok(ref, 'downloaded and created a player');
79 | window.setTimeout(() => {
80 | ReactDOM.unmountComponentAtNode(this.fixture);
81 | done();
82 | }, 1);
83 | }
84 | }),
85 | this.fixture
86 | );
87 |
88 | assert.ok(reactPlayerLoader, 'player loader react component created');
89 | });
90 |
91 | QUnit.module('attrs');
92 |
93 | QUnit.test('can set attributes on the component element', function(assert) {
94 | const done = assert.async();
95 |
96 | assert.expect(1);
97 |
98 | ReactDOM.render(
99 | React.createElement(ReactPlayerLoader, {
100 | accountId: '1',
101 | attrs: {foo: 'bar'},
102 | onSuccess: ({ref, type}) => {
103 | window.setTimeout(done, 1);
104 | }
105 | }),
106 | this.fixture
107 | );
108 |
109 | assert.ok(this.fixture.querySelector('div[foo="bar"]'), 'foo="bar" div exists');
110 | });
111 |
112 | QUnit.test('className defaults to "brightcove-react-player-loader"', function(assert) {
113 | const done = assert.async();
114 |
115 | assert.expect(1);
116 |
117 | ReactDOM.render(
118 | React.createElement(ReactPlayerLoader, {
119 | accountId: '1',
120 | attrs: {id: 'test'},
121 | onSuccess: ({ref, type}) => {
122 | window.setTimeout(done, 1);
123 | }
124 | }),
125 | this.fixture
126 | );
127 |
128 | const el = this.fixture.querySelector('#test');
129 |
130 | assert.strictEqual(el.className, 'brightcove-react-player-loader', 'component element has correct className');
131 | });
132 |
133 | QUnit.test('className can be set to override default', function(assert) {
134 | const done = assert.async();
135 |
136 | assert.expect(1);
137 |
138 | ReactDOM.render(
139 | React.createElement(ReactPlayerLoader, {
140 | accountId: '1',
141 | attrs: {
142 | className: 'foo bar',
143 | id: 'test'
144 | },
145 | onSuccess: ({ref, type}) => {
146 | window.setTimeout(done, 1);
147 | }
148 | }),
149 | this.fixture
150 | );
151 |
152 | const el = this.fixture.querySelector('#test');
153 |
154 | assert.strictEqual(el.className, 'foo bar', 'component element has correct className');
155 | });
156 |
157 | QUnit.module('props', {
158 | beforeEach() {
159 |
160 | // This mock catalog plugin takes custom options for testing purposes.
161 | // The ReactPlayerLoader expects that the player will have initialized
162 | // the Video Cloud Catalog plugin itself.
163 | //
164 | // The custom options for the mock plugin instruct it whether or not to
165 | // produce "error" cases.
166 | this.mockCatalogPlugin = function(options = {}) {
167 | const err = new Error('mock catalog request failure');
168 |
169 | this.catalog = {
170 | getLazySequence(seq, cb, adConfigId) {
171 | if (options.error) {
172 | cb(err, null);
173 | } else {
174 | cb(null, options.data || [{foo: 1}]);
175 | }
176 | },
177 | get(params, cb) {
178 | let result = {foo: 1};
179 |
180 | if (params.type === 'playlist' || params.type === 'search') {
181 | result = [result, {foo: 2}];
182 | }
183 |
184 | if (options.error) {
185 | cb(err, null);
186 | } else {
187 | cb(null, options.data || result);
188 | }
189 | },
190 | load(data) {
191 | return data;
192 | }
193 | };
194 |
195 | Object.keys(this.catalog).forEach(key => sinon.spy(this.catalog, key));
196 | };
197 |
198 | this.mockPlaylistPlugin = function(list = [], currentItem = 0) {
199 | this.playlist = (l, i) => {
200 | if (l !== undefined) {
201 | list = l;
202 | if (i > -1) {
203 | currentItem = i;
204 | }
205 | }
206 | return list;
207 | };
208 |
209 | this.playlist.currentItem = (i) => {
210 | if (i !== undefined) {
211 | currentItem = i;
212 | }
213 | return currentItem;
214 | };
215 |
216 | sinon.spy(this.playlist, 'currentItem');
217 | };
218 | },
219 | afterEach() {
220 | this.mockCatalogPlugin = null;
221 | this.mockPlaylistPlugin = null;
222 | }
223 | });
224 |
225 | QUnit.test('change catalogSearch, success response', function(assert) {
226 | const done = assert.async();
227 |
228 | let rerender;
229 |
230 | const props = {
231 | accountId: '1',
232 | adConfigId: 'abc-123',
233 | deliveryConfigId: 'def-456',
234 | catalogSearch: '2',
235 | onSuccess: ({ref, type}) => {
236 | window.videojs.registerPlugin('catalog', this.mockCatalogPlugin);
237 | ref.catalog();
238 |
239 | props.catalogSearch = '3';
240 |
241 | rerender(React.createElement(ReactPlayerLoader, props));
242 |
243 | assert.ok(ref.catalog.get.calledOnce, 'player.catalog.get was called');
244 | assert.ok(ref.catalog.load.calledOnce, 'player.catalog.load was called');
245 |
246 | const params = ref.catalog.get.getCall(0).args[0];
247 |
248 | assert.deepEqual(params, {
249 | adConfigId: 'abc-123',
250 | deliveryConfigId: 'def-456',
251 | q: '3',
252 | type: 'search'
253 | }, 'catalog params were correct');
254 |
255 | done();
256 | }
257 | };
258 |
259 | rerender = render(React.createElement(ReactPlayerLoader, props)).rerender;
260 | });
261 |
262 | QUnit.test('change catalogSearch, failure response', function(assert) {
263 | const done = assert.async();
264 |
265 | let rerender;
266 |
267 | const props = {
268 | accountId: '1',
269 | catalogSearch: '2',
270 | onSuccess: ({ref, type}) => {
271 | window.videojs.registerPlugin('catalog', this.mockCatalogPlugin);
272 | ref.catalog({error: true});
273 |
274 | props.catalogSearch = '3';
275 |
276 | rerender(React.createElement(ReactPlayerLoader, props));
277 |
278 | assert.ok(ref.catalog.get.calledOnce, 'player.catalog.get was called');
279 | assert.ok(ref.catalog.load.notCalled, 'player.catalog.load was not called');
280 |
281 | done();
282 | }
283 | };
284 |
285 | rerender = render(React.createElement(ReactPlayerLoader, props)).rerender;
286 | });
287 |
288 | QUnit.test('change catalogSequence, success response', function(assert) {
289 | const done = assert.async();
290 |
291 | let rerender;
292 |
293 | const props = {
294 | accountId: '1',
295 | adConfigId: 'abc-123',
296 | deliveryConfigId: 'def-456',
297 | catalogSequence: [{}],
298 | onSuccess: ({ref, type}) => {
299 | window.videojs.registerPlugin('catalog', this.mockCatalogPlugin);
300 | ref.catalog();
301 |
302 | props.catalogSequence = [{}, {}];
303 |
304 | rerender(React.createElement(ReactPlayerLoader, props));
305 |
306 | assert.ok(ref.catalog.get.notCalled, 'player.catalog.get was not called');
307 | assert.ok(ref.catalog.getLazySequence.calledOnce, 'player.catalog.getLazySequence was called');
308 | assert.ok(ref.catalog.load.calledOnce, 'player.catalog.load was called');
309 |
310 | const seq = ref.catalog.getLazySequence.getCall(0).args[0];
311 |
312 | assert.deepEqual(seq, props.catalogSequence, 'catalog sequence was correct');
313 |
314 | done();
315 | }
316 | };
317 |
318 | rerender = render(React.createElement(ReactPlayerLoader, props)).rerender;
319 | });
320 |
321 | QUnit.test('change catalogSequence, failure response', function(assert) {
322 | const done = assert.async();
323 |
324 | let rerender;
325 |
326 | const props = {
327 | accountId: '1',
328 | catalogSequence: [{}],
329 | onSuccess: ({ref, type}) => {
330 | window.videojs.registerPlugin('catalog', this.mockCatalogPlugin);
331 | ref.catalog({error: true});
332 |
333 | props.catalogSequence = [{}, {}];
334 |
335 | rerender(React.createElement(ReactPlayerLoader, props));
336 |
337 | assert.ok(ref.catalog.get.notCalled, 'player.catalog.get was not called');
338 | assert.ok(ref.catalog.getLazySequence.calledOnce, 'player.catalog.getLazySequence was called');
339 | assert.ok(ref.catalog.load.notCalled, 'player.catalog.load was not called');
340 |
341 | done();
342 | }
343 | };
344 |
345 | rerender = render(React.createElement(ReactPlayerLoader, props)).rerender;
346 | });
347 |
348 | QUnit.test('change playlistId, success response', function(assert) {
349 | const done = assert.async();
350 |
351 | let rerender;
352 |
353 | const props = {
354 | accountId: '1',
355 | adConfigId: 'abc-123',
356 | deliveryConfigId: 'def-456',
357 | playlistId: '2',
358 | onSuccess: ({ref, type}) => {
359 | window.videojs.registerPlugin('catalog', this.mockCatalogPlugin);
360 | ref.catalog();
361 |
362 | props.playlistId = '3';
363 |
364 | rerender(React.createElement(ReactPlayerLoader, props));
365 |
366 | assert.ok(ref.catalog.get.calledOnce, 'player.catalog.get was called');
367 | assert.ok(ref.catalog.load.calledOnce, 'player.catalog.load was called');
368 |
369 | const params = ref.catalog.get.getCall(0).args[0];
370 |
371 | assert.deepEqual(params, {
372 | adConfigId: 'abc-123',
373 | deliveryConfigId: 'def-456',
374 | id: '3',
375 | type: 'playlist'
376 | }, 'catalog params were correct');
377 |
378 | done();
379 | }
380 | };
381 |
382 | rerender = render(React.createElement(ReactPlayerLoader, props)).rerender;
383 | });
384 |
385 | QUnit.test('change playlistId, failure response', function(assert) {
386 | const done = assert.async();
387 |
388 | let rerender;
389 |
390 | const props = {
391 | accountId: '1',
392 | playlistId: '2',
393 | onSuccess: ({ref, type}) => {
394 | window.videojs.registerPlugin('catalog', this.mockCatalogPlugin);
395 | ref.catalog({error: true});
396 |
397 | props.playlistId = '3';
398 |
399 | rerender(React.createElement(ReactPlayerLoader, props));
400 |
401 | assert.ok(ref.catalog.get.calledOnce, 'player.catalog.get was called');
402 | assert.ok(ref.catalog.load.notCalled, 'player.catalog.load was not called');
403 |
404 | done();
405 | }
406 | };
407 |
408 | rerender = render(React.createElement(ReactPlayerLoader, props)).rerender;
409 | });
410 |
411 | QUnit.test('change videoId, success response', function(assert) {
412 | const done = assert.async();
413 |
414 | let rerender;
415 |
416 | const props = {
417 | accountId: '1',
418 | adConfigId: 'abc-123',
419 | deliveryConfigId: 'def-456',
420 | videoId: '2',
421 | onSuccess: ({ref, type}) => {
422 | window.videojs.registerPlugin('catalog', this.mockCatalogPlugin);
423 | ref.catalog();
424 |
425 | props.videoId = '3';
426 |
427 | rerender(React.createElement(ReactPlayerLoader, props));
428 |
429 | assert.ok(ref.catalog.get.calledOnce, 'player.catalog.get was called');
430 | assert.ok(ref.catalog.load.calledOnce, 'player.catalog.load was called');
431 |
432 | const params = ref.catalog.get.getCall(0).args[0];
433 |
434 | assert.deepEqual(params, {
435 | adConfigId: 'abc-123',
436 | deliveryConfigId: 'def-456',
437 | id: '3',
438 | type: 'video'
439 | }, 'catalog params were correct');
440 |
441 | done();
442 | }
443 | };
444 |
445 | rerender = render(React.createElement(ReactPlayerLoader, props)).rerender;
446 | });
447 |
448 | QUnit.test('change videoId, failure response', function(assert) {
449 | const done = assert.async();
450 |
451 | let rerender;
452 |
453 | const props = {
454 | accountId: '1',
455 | videoId: '2',
456 | onSuccess: ({ref, type}) => {
457 | window.videojs.registerPlugin('catalog', this.mockCatalogPlugin);
458 | ref.catalog({error: true});
459 |
460 | props.videoId = '3';
461 |
462 | rerender(React.createElement(ReactPlayerLoader, props));
463 |
464 | assert.ok(ref.catalog.get.calledOnce, 'player.catalog.get was called');
465 | assert.ok(ref.catalog.load.notCalled, 'player.catalog.load was not called');
466 |
467 | done();
468 | }
469 | };
470 |
471 | rerender = render(React.createElement(ReactPlayerLoader, props)).rerender;
472 | });
473 |
474 | QUnit.test('change from one catalog request type to another ignores previous props', function(assert) {
475 | const done = assert.async();
476 |
477 | let rerender;
478 |
479 | const props = {
480 | accountId: '1',
481 | adConfigId: 'abc-123',
482 | deliveryConfigId: 'def-456',
483 | catalogSearch: '0',
484 | catalogSequence: '1',
485 | playlistId: '2',
486 | videoId: '3',
487 | onSuccess: ({ref, type}) => {
488 | window.videojs.registerPlugin('catalog', this.mockCatalogPlugin);
489 | ref.catalog();
490 |
491 | props.playlistId = '4';
492 |
493 | rerender(React.createElement(ReactPlayerLoader, props));
494 |
495 | assert.ok(ref.catalog.get.calledOnce, 'player.catalog.get was called');
496 | assert.ok(ref.catalog.load.calledOnce, 'player.catalog.load was called');
497 |
498 | const params = ref.catalog.get.getCall(0).args[0];
499 |
500 | assert.deepEqual(params, {
501 | adConfigId: 'abc-123',
502 | deliveryConfigId: 'def-456',
503 | id: '4',
504 | type: 'playlist'
505 | }, 'props that trigger a type of catalog request and were not changed were not included');
506 |
507 | done();
508 | }
509 | };
510 |
511 | rerender = render(React.createElement(ReactPlayerLoader, props)).rerender;
512 | });
513 |
514 | QUnit.test('changing playlistId + playlistVideoId, success response', function(assert) {
515 | const done = assert.async();
516 |
517 | let rerender;
518 |
519 | const props = {
520 | accountId: '1',
521 | embedOptions: {unminified: true},
522 | playlistId: '0',
523 | playlistVideoId: '0',
524 | onSuccess: ({ref, type}) => {
525 | const playlist = [{
526 | id: 1
527 | }, {
528 | id: 2
529 | }];
530 |
531 | window.videojs.registerPlugin('catalog', this.mockCatalogPlugin);
532 | ref.catalog({data: playlist});
533 |
534 | props.playlistId = '1';
535 | props.playlistVideoId = 2;
536 |
537 | rerender(React.createElement(ReactPlayerLoader, props));
538 |
539 | assert.ok(ref.catalog.get.calledOnce, 'player.catalog.get was called');
540 | assert.ok(ref.catalog.load.calledOnce, 'player.catalog.load was called');
541 |
542 | const params = ref.catalog.get.getCall(0).args[0];
543 |
544 | assert.deepEqual(params, {
545 | id: '1',
546 | type: 'playlist'
547 | }, 'catalog params were correct');
548 |
549 | // The test players have the playlist plugin, but we want a minimal
550 | // test case for playlists; so, we remove it and mock a new plugin.
551 | window.videojs.getPlugin('plugin').deregisterPlugin('playlist');
552 | delete ref.playlist;
553 |
554 | window.videojs.registerPlugin('playlist', this.mockPlaylistPlugin);
555 | ref.playlist(playlist, playlist.startingIndex);
556 |
557 | assert.strictEqual(ref.playlist.currentItem(), 1, 'the second playlist item is selected');
558 |
559 | done();
560 | }
561 | };
562 |
563 | rerender = render(React.createElement(ReactPlayerLoader, props)).rerender;
564 | });
565 |
566 | QUnit.test('changing playlistVideoId on an already loaded playlist, using referenceId instead of id', function(assert) {
567 | const done = assert.async();
568 |
569 | let rerender;
570 |
571 | const props = {
572 | accountId: '1',
573 | playlistId: '0',
574 | playlistVideoId: '0',
575 | onSuccess: ({ref, type}) => {
576 |
577 | // The test players have the playlist plugin, but we want a minimal
578 | // test case for playlists; so, we remove it and mock a new plugin.
579 | window.videojs.getPlugin('plugin').deregisterPlugin('playlist');
580 | delete ref.playlist;
581 |
582 | window.videojs.registerPlugin('playlist', this.mockPlaylistPlugin);
583 | ref.playlist([{
584 | id: 1
585 | }, {
586 | id: 2
587 | }, {
588 | id: 3,
589 | referenceId: 'foo'
590 | }]);
591 |
592 | props.playlistVideoId = 'ref:foo';
593 |
594 | rerender(React.createElement(ReactPlayerLoader, props));
595 |
596 | assert.strictEqual(ref.playlist.currentItem(), 2, 'the third playlist item is selected');
597 |
598 | done();
599 | }
600 | };
601 |
602 | rerender = render(React.createElement(ReactPlayerLoader, props)).rerender;
603 | });
604 |
605 | QUnit.test('other prop changes reload the player', function(assert) {
606 | const done = assert.async();
607 |
608 | let rerender;
609 |
610 | const props = {
611 | accountId: '1',
612 | applicationId: 'foo',
613 | onSuccess: ({ref, type}) => {
614 | if (props.applicationId === 'bar') {
615 | assert.ok(true, 'the success callback was called a second time because the player was re-loaded');
616 | done();
617 | }
618 |
619 | props.applicationId = 'bar';
620 | rerender(React.createElement(ReactPlayerLoader, props));
621 | }
622 | };
623 |
624 | rerender = render(React.createElement(ReactPlayerLoader, props)).rerender;
625 | });
626 |
627 | QUnit.test('loadPlayer() method reloads player', function(assert) {
628 | const done = assert.async(2);
629 |
630 | const props = {
631 | accountId: '1',
632 | applicationId: 'foo',
633 | onSuccess: ({ref, type}) => {
634 | assert.ok(true, 'the success callback was called');
635 | done();
636 | }
637 | };
638 |
639 | const reactPlayerLoader = ReactDOM.render(
640 | React.createElement(ReactPlayerLoader, props),
641 | this.fixture
642 | );
643 |
644 | // Add a delay, otherwise this is emulating the strict mode problem
645 | window.setTimeout(function() {
646 | reactPlayerLoader.loadPlayer();
647 | }, 1000);
648 | });
649 |
650 | QUnit.test('set manualReloadFromPropChanges to true', function(assert) {
651 | const done = assert.async(2);
652 | let rerender;
653 | const props = {
654 | accountId: '1',
655 | applicationId: 'foo',
656 | manualReloadFromPropChanges: true,
657 | onSuccess: ({ref, type}) => {
658 | if (props.applicationId !== 'bar') {
659 | props.applicationId = 'bar';
660 | done();
661 | }
662 | rerender(React.createElement(ReactPlayerLoader, props));
663 | assert.ok(true, 'the success callback was called');
664 | done();
665 | }
666 | };
667 |
668 | rerender = render(React.createElement(ReactPlayerLoader, props)).rerender;
669 | });
670 |
671 | QUnit.test('set manualReloadFromPropChanges to false', function(assert) {
672 | const done = assert.async(3);
673 | let rerender;
674 | const props = {
675 | accountId: '1',
676 | applicationId: 'foo',
677 | manualReloadFromPropChanges: false,
678 | onSuccess: ({ref, type}) => {
679 | if (props.applicationId !== 'bar') {
680 | props.applicationId = 'bar';
681 | done();
682 | }
683 | rerender(React.createElement(ReactPlayerLoader, props));
684 | assert.ok(true, 'the success callback was called');
685 | done();
686 | }
687 | };
688 |
689 | rerender = render(React.createElement(ReactPlayerLoader, props)).rerender;
690 | });
691 |
692 | QUnit.module('strict mode');
693 |
694 | QUnit.test('player not created twice in strict mode', function(assert) {
695 | const done = assert.async();
696 |
697 | assert.expect(1);
698 |
699 | // createRoot needed for React 18
700 | const root = ReactDOM.createRoot(this.fixture);
701 |
702 | root.render(React.createElement(
703 | React.StrictMode,
704 | {},
705 | React.createElement(ReactPlayerLoader, {
706 | accountId: '1',
707 | onSuccess: ({ref, type}) => {
708 | assert.ok(ref, 'downloaded and created a player');
709 | }
710 | })
711 | ));
712 |
713 | window.setTimeout(function() {
714 | root.unmount();
715 | done();
716 | }, 1000);
717 |
718 | });
719 |
720 | QUnit.test('aborted player load logs message', function(assert) {
721 | const done = assert.async();
722 | const spy = sinon.spy(window.console, 'log');
723 |
724 | assert.expect(1);
725 |
726 | // createRoot needed for React 18
727 | const root = ReactDOM.createRoot(this.fixture);
728 |
729 | root.render(React.createElement(
730 | React.StrictMode,
731 | {},
732 | React.createElement(ReactPlayerLoader, {
733 | accountId: '1'
734 | })
735 | ));
736 |
737 | window.setTimeout(function() {
738 | assert.ok(
739 | spy.calledWith('Brightcove React Player Loader aborted a subsequent player load while a load was pending'),
740 | 'warning logged'
741 | );
742 | root.unmount();
743 | spy.restore();
744 | done();
745 | }, 1000);
746 | });
747 | });
748 |
--------------------------------------------------------------------------------