├── .eslintignore ├── .eslintrc ├── .github ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── run_canary.yaml │ ├── run_patch.yaml │ ├── run_prod.yaml │ └── run_tests.yaml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── api-extractor.json ├── api-extractor ├── playkit-js.api.json ├── report-temp │ └── playkit-js.api.md └── report │ └── playkit-js.api.md ├── demo └── index.html ├── docs ├── api.md ├── autoplay.md ├── configuration.md ├── drm-system.md └── source-selection-logic.md ├── flow-typed ├── classes │ └── player.js ├── interfaces │ ├── dom.js │ ├── drm-protocol.js │ ├── engine-capabilty.js │ ├── engine-decorator.js │ ├── engine.js │ └── media-source-adapter.js ├── modules │ ├── js-logger.js │ └── ua-parser.js └── types │ ├── abr-config.js │ ├── abr-modes.js │ ├── ad-break-options.js │ ├── ad-break-types.js │ ├── ad-options.js │ ├── ad-tag-types.js │ ├── auto-play-types.js │ ├── cors-types.js │ ├── custom-labels-config.js │ ├── deferred-promise.js │ ├── dimensions.js │ ├── drm-config.js │ ├── drm-data.js │ ├── engine-decorator-provider.js │ ├── engine-types.js │ ├── event-types.js │ ├── exteranl-thumbnails-object.js │ ├── external-caption-object.js │ ├── image-player-options.js │ ├── log-level.js │ ├── media-source-capabilities.js │ ├── media-source-options.js │ ├── media-source.js │ ├── media-types.js │ ├── metadata-config.js │ ├── mime-types.js │ ├── network-config.js │ ├── play-options.js │ ├── playback-config.js │ ├── playback-options.js │ ├── player-options.js │ ├── player-state.js │ ├── prefer-native-config.js │ ├── request-types.js │ ├── request.js │ ├── response.js │ ├── restrictions-types.js │ ├── screen-orientation-type.js │ ├── session-config.js │ ├── sources-config.js │ ├── state-types.js │ ├── stats.js │ ├── stream-priority.js │ ├── stream-types.js │ ├── streaming-config.js │ ├── text-config.js │ ├── text-style.js │ ├── text-track-cue.js │ ├── text-track-display-setting.js │ ├── thumbnail-vtt-cue.js │ ├── track-types.js │ ├── video-dimensions.js │ └── video-element-store.js ├── karma.conf.js ├── package.json ├── src ├── ads │ ├── ad-break-type.ts │ ├── ad-error-code.ts │ ├── ad-event-type.ts │ └── ad-tag-type.ts ├── assets │ ├── encoding-sources.json │ └── style.css ├── drm │ ├── drm-scheme.ts │ └── fairplay.ts ├── engines │ ├── dropped-frames-watcher.ts │ ├── engine-decorator-manager.ts │ ├── engine-decorator-provider.ts │ ├── engine-decorator.ts │ ├── engine-provider.ts │ ├── engine-type.ts │ ├── html5 │ │ ├── capabilities │ │ │ └── html5-autoplay.ts │ │ ├── cors-types.ts │ │ ├── html5.ts │ │ └── media-source │ │ │ ├── adapters │ │ │ ├── fairplay-drm-handler.ts │ │ │ ├── native-adapter-default-config.json │ │ │ └── native-adapter.ts │ │ │ ├── base-media-source-adapter.ts │ │ │ └── media-source-provider.ts │ └── stream-type.ts ├── enums │ ├── auto-play-type.ts │ ├── media-type.ts │ ├── mime-type.ts │ ├── request-type.ts │ └── screen-orientation-type.ts ├── error │ ├── category.ts │ ├── code.ts │ ├── error.ts │ └── severity.ts ├── event │ ├── event-manager.ts │ ├── event-type.ts │ ├── fake-event-target.ts │ └── fake-event.ts ├── fullscreen │ └── fullscreen-controller.ts ├── index.html ├── middleware │ ├── base-middleware.ts │ ├── middleware.ts │ └── playback-middleware.ts ├── player-config.js ├── player.ts ├── playkit.ts ├── state │ ├── state-manager.ts │ ├── state-type.ts │ └── state.ts ├── thumbnail │ ├── external-thumbnails-handler.ts │ └── thumbnail-info.ts ├── track │ ├── abr-mode-type.ts │ ├── audio-track.ts │ ├── external-captions-handler.ts │ ├── image-track.ts │ ├── label-options.ts │ ├── text-style.ts │ ├── text-track-display.ts │ ├── text-track.ts │ ├── timed-metadata.ts │ ├── track-type.ts │ ├── track.ts │ ├── video-track.ts │ └── vtt-region.ts ├── types │ ├── abr-config.ts │ ├── ad-break-options.ts │ ├── ad-options.ts │ ├── auto-play-types.ts │ ├── custom-labels-config.ts │ ├── deferred-promise.ts │ ├── dimensions.ts │ ├── drm-config.ts │ ├── drm-data.ts │ ├── engine-decorator-provider.ts │ ├── event-types.ts │ ├── exteranl-thumbnails-object.ts │ ├── external-caption-object.ts │ ├── global │ │ └── globals.d.ts │ ├── image-player-options.ts │ ├── index.ts │ ├── interfaces │ │ ├── drm-protocol.ts │ │ ├── engine-capabilty.ts │ │ ├── engine-decorator.ts │ │ ├── engine.ts │ │ ├── index.ts │ │ └── media-source-adapter.ts │ ├── log-level.ts │ ├── logger-levels.ts │ ├── media-source-capabilities.ts │ ├── media-source-options.ts │ ├── media-source.ts │ ├── media-types.ts │ ├── metadata-config.ts │ ├── mime-types.ts │ ├── network-config.ts │ ├── play-options.ts │ ├── playback-config.ts │ ├── playback-options.ts │ ├── player-options.ts │ ├── player-state.ts │ ├── prefer-native-config.ts │ ├── request-types.ts │ ├── request.ts │ ├── response.ts │ ├── restrictions-types.ts │ ├── screen-orientation-type.ts │ ├── session-config.ts │ ├── sources-config.ts │ ├── stats.ts │ ├── stream-priority.ts │ ├── stream-types.ts │ ├── streaming-config.ts │ ├── text-config.ts │ ├── text-style.ts │ ├── text-track-cue.ts │ ├── text-track-display-setting.ts │ ├── thumbnail-vtt-cue.ts │ ├── track-types.ts │ ├── ua-parser.ts │ ├── video-dimensions.ts │ └── video-element-store.ts └── utils │ ├── binary-search.ts │ ├── env.ts │ ├── index.ts │ ├── jsonp.ts │ ├── locale.ts │ ├── logger.ts │ ├── multi-map.ts │ ├── poster-manager.ts │ ├── resize-watcher.ts │ ├── resolution.ts │ ├── restrictions.ts │ ├── styles.ts │ └── util.ts ├── tests ├── .eslintrc ├── assets │ ├── 00000002.jpg │ ├── 00000003.jpg │ ├── 00000004.jpg │ ├── 00000005.jpg │ ├── 00000006.jpg │ ├── 00000007.jpg │ ├── audios.mp4 │ ├── bbb-sprite.jpeg │ ├── en.vtt │ ├── he.vtt │ ├── heb.vtt │ ├── mov_bbb.mp4 │ ├── rus.vtt │ ├── thumbnails1.vtt │ ├── thumbnails2.vtt │ └── thumbnails3.vtt ├── configs │ ├── external-captions.json │ └── sources.json ├── e2e │ ├── dimensions.spec.js │ ├── drm │ │ ├── drm-scheme.spec.js │ │ └── fairplay.spec.js │ ├── engines │ │ ├── dropped-frames-watcher.spec.js │ │ ├── engine-deorator.spec.js │ │ ├── engine-provider.spec.js │ │ ├── html5 │ │ │ ├── html5.spec.js │ │ │ └── media-source │ │ │ │ ├── adapters │ │ │ │ ├── fairplay-drm-handler.spec.js │ │ │ │ ├── native-adapter.spec.js │ │ │ │ └── test-adapters │ │ │ │ │ └── test-adapters.ts │ │ │ │ └── media-source-provider.spec.js │ │ ├── test-engine-decorator-providers.ts │ │ └── test-engines.ts │ ├── error │ │ └── error.spec.js │ ├── event │ │ └── event-manager.spec.js │ ├── fullscreen │ │ └── fullscreen-controller.spec.js │ ├── middleware │ │ ├── middleware.spec.js │ │ └── playback-middleware.spec.js │ ├── player.spec.js │ ├── state │ │ ├── state-manger.spec.js │ │ ├── state.spec.js │ │ └── states.spec.js │ ├── thumbnail │ │ └── external-thumbnails-handler.spec.js │ ├── track │ │ ├── external-captions-handler.spec.js │ │ ├── timed-metadata.spec.js │ │ └── track.spec.js │ └── utils │ │ ├── binary-search.spec.js │ │ ├── logger.spec.js │ │ ├── poster-manager.spec.js │ │ ├── resize-watcher.spec.js │ │ ├── resolution.spec.js │ │ └── util.spec.js ├── index.js └── utils │ └── test-utils.js ├── tsconfig-lib.json ├── tsconfig.json ├── webpack.config.js └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | types/ 3 | demo/ 4 | webpack.config.js 5 | tsconfig.json -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"], 5 | "plugins": ["@typescript-eslint"], 6 | "rules": { 7 | "indent": ["error", 2], 8 | "react/prefer-stateless-function": "off", 9 | "max-len": ["warn", { "code": 500 }], 10 | "eol-last": "off", 11 | "@typescript-eslint/explicit-function-return-type": "warn", 12 | "@typescript-eslint/no-non-null-assertion": "off", 13 | "@typescript-eslint/no-unused-vars": "error", 14 | // "@typescript-eslint/no-explicit-any": "warn", 15 | "@typescript-eslint/no-explicit-any": "off", 16 | "@typescript-eslint/explicit-member-accessibility": [ 17 | "error", 18 | { 19 | "accessibility": "explicit", 20 | "overrides": { 21 | "accessors": "explicit", 22 | "constructors": "no-public", 23 | "methods": "explicit", 24 | "properties": "explicit", 25 | "parameterProperties": "explicit" 26 | } 27 | } 28 | ], 29 | "block-scoped-var": "error", 30 | "eqeqeq": "error", 31 | "no-var": "error", 32 | "no-console": "error", 33 | "prefer-const": "error", 34 | "prefer-arrow-callback": "error", 35 | "no-trailing-spaces": "error", 36 | "quotes": ["warn", "single", { "avoidEscape": true }], 37 | "no-restricted-properties": [ 38 | "error", 39 | { 40 | "object": "describe", 41 | "property": "only" 42 | }, 43 | { 44 | "object": "it", 45 | "property": "only" 46 | } 47 | ] 48 | }, 49 | "overrides": [], 50 | "settings": { 51 | "jest": { 52 | "version": 26 53 | } 54 | }, 55 | "env": { 56 | "browser": true, 57 | "commonjs": true, 58 | "es6": true 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ##### Prerequisites 4 | 5 | - [ ] Have you checked for duplicate [issues](https://github.com/kaltura/playkit-js/issues): **\_\_** 6 | - [ ] Which Player [version](https://github.com/kaltura/playkit-js/releases) are you using: **\_\_** 7 | - [ ] Can you reproduce the issue with our latest release version: **\_\_** 8 | - [ ] Can you reproduce the issue with the latest code from master: **\_\_** 9 | - [ ] What browser and OS names and versions are you using: **\_\_** 10 | - [ ] If applicable, add test code or test page to reproduce: 11 | 12 | ``` 13 | Paste test code here 14 | ``` 15 | 16 | ##### Expected behavior 17 | 18 | What you expected to happen 19 | 20 | ##### Actual behavior 21 | 22 | What actually happened 23 | 24 | ##### Console output 25 | 26 | ``` 27 | Paste the contents of the browser console here. 28 | ``` 29 | 30 | ``` 31 | For media errors reported on Chrome browser, please also paste the output of chrome://media-internals 32 | ``` 33 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Description of the Changes 2 | 3 | Please add a detailed description of the change, whether it's an enhancement or a bugfix. 4 | If the PR is related to an open issue please link to it. 5 | 6 | ### CheckLists 7 | 8 | - [ ] changes have been done against master branch, and PR does not conflict 9 | - [ ] new unit / functional tests have been added (whenever applicable) 10 | - [ ] test are passing in local environment 11 | - [ ] Travis tests are passing (or test results are not worse than on master branch :)) 12 | - [ ] Docs have been updated 13 | -------------------------------------------------------------------------------- /.github/workflows/run_canary.yaml: -------------------------------------------------------------------------------- 1 | ## Canary CI/CD 2 | name: Canary 3 | run-name: Canary 4 | 5 | on: 6 | push: 7 | branches: 8 | - master 9 | - patch-version 10 | paths-ignore: 11 | - '.github/workflows/**' 12 | 13 | workflow_dispatch: 14 | inputs: 15 | branch: 16 | description: 'branch name' 17 | required: false 18 | default: 'master' 19 | 20 | jobs: 21 | canary: 22 | if: ${{ github.actor != 'PlaykitJs-Bot' }} 23 | uses: kaltura/playkit-js-common/.github/workflows/canary_dependency.yaml@master 24 | secrets: inherit 25 | with: 26 | node-version: "20.x" 27 | schema-type: "playerV3Versions" 28 | tests-yarn-run-to-execute: 'build lint type-check test' 29 | -------------------------------------------------------------------------------- /.github/workflows/run_patch.yaml: -------------------------------------------------------------------------------- 1 | name: Patch 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | npmTag: 7 | description: 'Select NPM Tag' 8 | required: true 9 | type: choice 10 | options: 11 | - experimental 12 | - patch 13 | default: 'patch' 14 | branch: 15 | description: 'branch name' 16 | required: true 17 | default: 'patch-version' 18 | 19 | jobs: 20 | update-tag: 21 | runs-on: ubuntu-latest 22 | 23 | steps: 24 | - name: Checkout Repository 25 | uses: actions/checkout@v3 26 | 27 | - name: Setup Node.js 28 | uses: actions/setup-node@v3 29 | with: 30 | node-version: "20.x" 31 | registry-url: 'https://registry.npmjs.org/' 32 | 33 | - name: Install Dependencies 34 | run: yarn install 35 | 36 | - name: Build Project 37 | run: yarn build 38 | 39 | - name: Publish to NPM 40 | run: | 41 | echo "//registry.npmjs.org/:_authToken=$NODE_AUTH_TOKEN" > .npmrc 42 | npm publish --tag ${{ github.event.inputs.npmTag }} 43 | rm -rf .npmrc 44 | env: 45 | NPM_AUTH_TOKEN: ${{ secrets.PLAYER_NPM_TOKEN }} 46 | NPM_TOKEN: ${{ secrets.PLAYER_NPM_TOKEN }} 47 | NODE_AUTH_TOKEN: ${{ secrets.PLAYER_NPM_TOKEN }} 48 | 49 | -------------------------------------------------------------------------------- /.github/workflows/run_prod.yaml: -------------------------------------------------------------------------------- 1 | ## Prod CI 2 | name: Prod 3 | run-name: Prod 4 | 5 | on: 6 | workflow_dispatch: 7 | 8 | jobs: 9 | prod: 10 | uses: kaltura/ovp-pipelines-pub/.github/workflows/player_cicd.yaml@master 11 | secrets: 12 | PLAYER_CENTRAL_ACCOUNT_ID: ${{ secrets.PLAYER_CENTRAL_ACCOUNT_ID }} 13 | PLAYER_SERVICES_ACCOUNT_ID: ${{ secrets.PLAYER_SERVICES_ACCOUNT_ID }} 14 | PLAYER_S3_BUCKET_DEPLOYMENT: ${{ secrets.PLAYER_S3_BUCKET_DEPLOYMENT }} 15 | PLAYER_S3_BUCKET_APPS: ${{ secrets.PLAYER_S3_BUCKET_APPS }} 16 | PLAYER_NPM_TOKEN: ${{ secrets.PLAYER_NPM_TOKEN }} 17 | PLAYER_LAMBDA_NAME: ${{ secrets.PLAYER_LAMBDA_NAME }} 18 | PLAYER_MSTEAMS_WEBHOOK: ${{ secrets.PLAYER_MSTEAMS_WEBHOOK }} 19 | PLAYER_GITHUB_BOT_TOKEN: ${{ secrets.PLAYER_GITHUB_BOT_TOKEN }} 20 | with: 21 | node-version: "20.x" 22 | type: "dependency" 23 | env: "prod" 24 | schema-type: "playerV3Versions" 25 | tests-yarn-run-to-execute: 'build lint type-check test' 26 | -------------------------------------------------------------------------------- /.github/workflows/run_tests.yaml: -------------------------------------------------------------------------------- 1 | ## CI - Player And Plugin Tests 2 | name: Player And Plugin Tests 3 | run-name: Player And Plugin Tests 4 | 5 | on: 6 | pull_request: 7 | branches: 8 | - "*" 9 | paths-ignore: 10 | - '.github/workflows/**' 11 | jobs: 12 | build: 13 | uses: kaltura/ovp-pipelines-pub/.github/workflows/player_tests.yaml@master 14 | with: 15 | node-version: '20.x' 16 | yarn-run-to-execute: 'build' 17 | test: 18 | uses: kaltura/ovp-pipelines-pub/.github/workflows/player_tests.yaml@master 19 | with: 20 | node-version: '20.x' 21 | yarn-run-to-execute: 'test' 22 | type-check: 23 | uses: kaltura/ovp-pipelines-pub/.github/workflows/player_tests.yaml@master 24 | with: 25 | node-version: '20.x' 26 | yarn-run-to-execute: 'type-check' 27 | build-types: 28 | uses: kaltura/ovp-pipelines-pub/.github/workflows/player_tests.yaml@master 29 | with: 30 | node-version: '20.x' 31 | yarn-run-to-execute: 'build:types' 32 | lint: 33 | uses: kaltura/ovp-pipelines-pub/.github/workflows/player_tests.yaml@master 34 | with: 35 | node-version: '20.x' 36 | yarn-run-to-execute: 'lint' 37 | notification: 38 | if: always() 39 | uses: kaltura/ovp-pipelines-pub/.github/workflows/notification.yaml@master 40 | needs: [build, test, type-check, lint] 41 | secrets: 42 | PLAYER_MSTEAMS_WEBHOOK: ${{ secrets.PLAYER_MSTEAMS_WEBHOOK }} 43 | with: 44 | failure-status: ${{ contains(needs.*.result, 'failure') }} 45 | cancelled-status: ${{ contains(needs.*.result, 'cancelled') }} 46 | is-test: 'true' 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | lib 3 | node_modules 4 | *.log 5 | api-extractor/report/ 6 | api-extractor/report-temp/ 7 | api-extractor/playkit-js.api.json 8 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | lib/ 3 | docs/ 4 | api-extractor/ 5 | .github/ 6 | flow-typed/ 7 | samples/ 8 | coverage/ 9 | CHANGELOG.md 10 | yarn.lock 11 | yarn-error.log 12 | LICENSE 13 | README.md 14 | 15 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 200, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": true, 6 | "singleQuote": true, 7 | "trailingComma": "none", 8 | "proseWrap": "preserve" 9 | } 10 | -------------------------------------------------------------------------------- /api-extractor.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", 3 | "mainEntryPointFilePath": "/lib/playkit.d.ts", 4 | "bundledPackages": [], 5 | "compiler": {}, 6 | "apiReport": { 7 | "enabled": true, 8 | "reportFolder": "/api-extractor/report", 9 | "reportTempFolder": "/api-extractor/report-temp" 10 | }, 11 | "docModel": { 12 | "enabled": true, 13 | "apiJsonFilePath": "/api-extractor/.api.json" 14 | }, 15 | "dtsRollup": { 16 | "enabled": true, 17 | "untrimmedFilePath": "/dist/.d.ts" 18 | }, 19 | "tsdocMetadata": {}, 20 | "messages": { 21 | "compilerMessageReporting": { 22 | "default": { 23 | "logLevel": "warning" 24 | } 25 | }, 26 | "extractorMessageReporting": { 27 | "default": { 28 | "logLevel": "warning" 29 | } 30 | }, 31 | "tsdocMessageReporting": { 32 | "default": { 33 | "logLevel": "warning" 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Title 6 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /docs/drm-system.md: -------------------------------------------------------------------------------- 1 | ## DRM System Selection 2 | 3 | Once the player gets an encrypted source to play, it will need to select a DRM system with which to run the source. 4 | The following article explains the default selection, and how an application may force the selection using a configuration. 5 | 6 | ### Default Selection 7 | 8 | By default, the player selects the DRM system according the source type and the browser capabilities, as follows: 9 | 10 | | dash on Chrome | dash on Firefox | dash on Edge | dash on IE | hls on Safari | 11 | | -------------- | --------------- | ------------ | ---------- | ------------- | 12 | | WIDEVINE | WIDEVINE | PLAYREADY | PLAYREADY | FAIRPLAY | 13 | 14 | ### Configuration 15 | 16 | Sometimes, an application may want to force the player to select a specific DRM system, usually to support DRM on browser that's not in the table above, or to prioritize between one of the supported DRM systems. 17 | 18 | Setting the desired DRM system is done by setting the DRM configuration. For example: 19 | 20 | ```ecmascript 6 21 | var config = { 22 | drm: { 23 | keySystem: playkit.core.DrmScheme.PLAYREADY // "com.microsoft.playready" 24 | }, 25 | sources: {...} 26 | }; 27 | var player = playkit.core.loadPlayer(config); 28 | ``` 29 | 30 | **Important**: 31 | The player selects the DRM system **after** the [source type selection](./source-selection-logic.md). 32 | Therefore, the DRM settings may **not be applied** when the source type cannot be played with the configured DRM system. For example: 33 | 34 | | Browser | Source Type | Default DRM System | Configured DRM System | Selected (reason) | 35 | | ------- | ----------- | ------------------ | --------------------- | ----------------------------------- | 36 | | Chrome | dash | WIDEVINE | PLAYREADY | PLAYREADY (dash+PLAYREADY is valid) | 37 | | Chrome | dash | WIDEVINE | FAIRPLAY | WIDEVINE (dash+FAIRPLAY is invalid) | 38 | -------------------------------------------------------------------------------- /docs/source-selection-logic.md: -------------------------------------------------------------------------------- 1 | ## How does the Source Selection Logic Works 2 | 3 | Let us assume that the following player configuration is given and we would like to understand how does the source selection logic works. 4 | 5 | ```js 6 | var config = { 7 | sources: { 8 | hls: [ 9 | { 10 | mimetype: 'application/x-mpegurl', 11 | url: 'http://wowzaec2demo.streamlock.net/vod-multitrack/_definst_/smil:ElephantsDream/ElephantsDream.smil/playlist.m3u8' 12 | } 13 | ] 14 | }, 15 | playback: { 16 | preferNative: { 17 | hls: true, 18 | dash: false 19 | }, 20 | streamPriority: [ 21 | { 22 | engine: 'html5', 23 | format: 'dash' 24 | }, 25 | { 26 | engine: 'html5', 27 | format: 'hls' 28 | } 29 | ] 30 | } 31 | }; 32 | ``` 33 | 34 | 1. The player looks at the `streamPriority` array and see that the `dash` stream has the highest priority. 35 | 2. The player will check whether it received any `dash` sources within the `sources` object. 36 | 3. Since there are no `dash` sources in it, the player will move to the next priority from the `streamPriority` list which is `hls`. 37 | 4. After checking the `sources` object again and found `hls` sources, the player will choose to play `hls`. 38 | 39 | However, will the player use the native hls or not? This depends on the `preferNative` value for `hls`. Because this value is set to `true`, the player knows that if the browser supports native `hls` playback, the source will be played natively. 40 | 41 | Given the configuration information above, which source will be played in each browser? 42 |
Following summarize the results for this scenario: 43 | 44 | | Chrome | Safari | Firefox | Edge | 45 | | ----------------- | ---------- | ----------------- | ---------- | 46 | | hls with `hls.js` | native hls | hls with `hls.js` | native hls | 47 | -------------------------------------------------------------------------------- /flow-typed/classes/player.js: -------------------------------------------------------------------------------- 1 | //@flow 2 | import Player from '../../src/player'; 3 | -------------------------------------------------------------------------------- /flow-typed/interfaces/dom.js: -------------------------------------------------------------------------------- 1 | declare interface HTMLVideoElement extends HTMLVideoElement { 2 | webkitSetPresentationMode: Function; 3 | requestPictureInPicture: Function; 4 | } 5 | 6 | declare interface HTMLElement extends HTMLElement { 7 | mozRequestFullScreen: Function; 8 | webkitRequestFullScreen: Function; 9 | msRequestFullScreen: Function; 10 | } 11 | 12 | declare interface Document extends Document { 13 | pictureInPictureEnabled: boolean; 14 | exitPictureInPicture: Function; 15 | } 16 | 17 | declare interface Navigator extends Navigator { 18 | userLanguage: string; 19 | } 20 | -------------------------------------------------------------------------------- /flow-typed/interfaces/drm-protocol.js: -------------------------------------------------------------------------------- 1 | //@flow 2 | declare interface IDrmProtocol { 3 | isConfigured(drmData: Array, drmConfig: PKDrmConfigObject): boolean; 4 | canPlayDrm(drmData: Array): boolean; 5 | setDrmPlayback(...any): void; 6 | } 7 | -------------------------------------------------------------------------------- /flow-typed/interfaces/engine-capabilty.js: -------------------------------------------------------------------------------- 1 | //@flow 2 | declare type CapabilityResult = {[capabilityName: string]: any}; 3 | 4 | declare interface ICapability { 5 | runCapability(): void; 6 | getCapability(): Promise; 7 | setCapabilities(capabilities: {[name: string]: any}): void; 8 | } 9 | -------------------------------------------------------------------------------- /flow-typed/interfaces/engine-decorator.js: -------------------------------------------------------------------------------- 1 | //@flow 2 | import VideoTrack from '../../src/track/video-track'; 3 | import AudioTrack from '../../src/track/audio-track'; 4 | import TextTrack from '../../src/track/text-track'; 5 | import FakeEvent from '../../src/event/fake-event'; 6 | 7 | declare interface IEngineDecorator { 8 | +active: boolean; 9 | dispatchEvent(event: FakeEvent): boolean; 10 | +restore?: (source: PKMediaSourceObject, config: Object) => void; 11 | +reset?: () => void; 12 | +destroy?: () => void; 13 | +attach?: () => void; 14 | +detach?: () => void; 15 | +getVideoElement?: () => HTMLVideoElement; 16 | +selectVideoTrack?: (videoTrack: VideoTrack) => void; 17 | +selectAudioTrack?: (audioTrack: AudioTrack) => void; 18 | +selectTextTrack?: (textTrack: TextTrack) => void; 19 | +hideTextTrack?: () => void; 20 | +enableAdaptiveBitrate?: () => void; 21 | +isAdaptiveBitrateEnabled?: () => boolean; 22 | +seekToLiveEdge?: () => void; 23 | +getStartTimeOfDvrWindow?: () => number; 24 | +isLive?: () => boolean; 25 | +play?: () => void; 26 | +pause?: () => void; 27 | +load?: (startTime: ?number) => Promise; 28 | +enterPictureInPicture?: () => void; 29 | +exitPictureInPicture?: () => void; 30 | +isPictureInPictureSupported?: () => boolean; 31 | +resetAllCues?: () => void; 32 | +attachMediaSource?: () => void; 33 | +detachMediaSource?: () => void; 34 | +id?: string; 35 | src?: string; 36 | currentTime?: number; 37 | +duration?: number; 38 | volume?: number; 39 | +paused?: boolean; 40 | +seeking?: boolean; 41 | +seekable?: TimeRanges; 42 | +played?: TimeRanges; 43 | +buffered?: TimeRanges; 44 | muted?: boolean; 45 | +defaultMuted?: boolean; 46 | poster?: string; 47 | preload?: string; 48 | autoplay?: boolean; 49 | loop?: boolean; 50 | controls?: boolean; 51 | +playbackRates?: Array; 52 | playbackRate?: number; 53 | defaultPlaybackRate?: number; 54 | +ended?: boolean; 55 | +error?: ?MediaError; 56 | +networkState?: number; 57 | +readyState?: number; 58 | +videoHeight?: number; 59 | +videoWidth?: number; 60 | playsinline?: boolean; 61 | crossOrigin?: ?string; 62 | +isInPictureInPicture?: boolean; 63 | +targetBuffer?: number; 64 | +availableBuffer?: number; 65 | } 66 | -------------------------------------------------------------------------------- /flow-typed/interfaces/engine.js: -------------------------------------------------------------------------------- 1 | //@flow 2 | import VideoTrack from '../../src/track/video-track'; 3 | import AudioTrack from '../../src/track/audio-track'; 4 | import TextTrack from '../../src/track/text-track'; 5 | import FakeEventTarget from '../../src/event/fake-event-target'; 6 | import {ThumbnailInfo} from '../../src/thumbnail/thumbnail-info'; 7 | import {ImageTrack} from '../../src/playkit'; 8 | 9 | declare interface IEngineStatic { 10 | id: string; 11 | createEngine(source: PKMediaSourceObject, config: Object, playerId: string): IEngine; 12 | canPlaySource(source: PKMediaSourceObject, preferNative: boolean, drmConfig: PKDrmConfigObject): boolean; 13 | runCapabilities(): void; 14 | getCapabilities(): Promise; 15 | setCapabilities(capabilities: {[name: string]: any}): void; 16 | prepareVideoElement(playerId: string): void; 17 | isSupported(): boolean; 18 | } 19 | 20 | declare interface IEngine extends FakeEventTarget { 21 | restore(source: PKMediaSourceObject, config: Object): void; 22 | destroy(): void; 23 | attach(): void; 24 | detach(): void; 25 | play(): ?Promise<*>; 26 | pause(): void; 27 | load(startTime: ?number): Promise; 28 | reset(): void; 29 | selectVideoTrack(videoTrack: VideoTrack): void; 30 | selectAudioTrack(audioTrack: AudioTrack): void; 31 | selectTextTrack(textTrack: TextTrack): void; 32 | selectImageTrack(imageTrack: ImageTrack): void; 33 | isPictureInPictureSupported(): boolean; 34 | enterPictureInPicture(): void; 35 | exitPictureInPicture(): void; 36 | hideTextTrack(): void; 37 | enableAdaptiveBitrate(): void; 38 | isAdaptiveBitrateEnabled(): boolean; 39 | applyABRRestriction(restrictions: PKABRRestrictionObject): void; 40 | seekToLiveEdge(): void; 41 | getStartTimeOfDvrWindow(): number; 42 | isLive(): boolean; 43 | getVideoElement(): HTMLVideoElement; 44 | resetAllCues(): void; 45 | attachMediaSource(): void; 46 | detachMediaSource(): void; 47 | getThumbnail(time: number): ?ThumbnailInfo; 48 | isOnLiveEdge(): boolean; 49 | addTextTrack(kind: string, label?: string, language?: string): ?TextTrack; 50 | getNativeTextTracks(): Array; 51 | getDrmInfo(): ?PKDrmDataObject; 52 | +id: string; 53 | currentTime: number; 54 | +duration: number; 55 | +liveDuration: number; 56 | volume: number; 57 | +paused: boolean; 58 | +seeking: boolean; 59 | +played: TimeRanges; 60 | +buffered: TimeRanges; 61 | +videoHeight: number; 62 | +videoWidth: number; 63 | muted: boolean; 64 | +defaultMuted: boolean; 65 | src: string; 66 | poster: string; 67 | preload: string; 68 | autoplay: boolean; 69 | controls: boolean; 70 | loop: boolean; 71 | +error: ?MediaError; 72 | +seekable: TimeRanges; 73 | +ended: boolean; 74 | playbackRate: number; 75 | +playbackRates: Array; 76 | defaultPlaybackRate: number; 77 | +isInPictureInPicture: boolean; 78 | +networkState: number; 79 | +readyState: number; 80 | +videoWidth: number; 81 | +videoHeight: number; 82 | playsinline: boolean; 83 | crossOrigin: ?string; 84 | +targetBuffer: number; 85 | +availableBuffer: number; 86 | } 87 | -------------------------------------------------------------------------------- /flow-typed/interfaces/media-source-adapter.js: -------------------------------------------------------------------------------- 1 | //@flow 2 | import VideoTrack from '../../src/track/video-track'; 3 | import AudioTrack from '../../src/track/audio-track'; 4 | import TextTrack from '../../src/track/text-track'; 5 | import {FakeEventTarget, ImageTrack} from '../../src/playkit'; 6 | 7 | declare interface IMediaSourceAdapterStatic { 8 | +id: string; 9 | isSupported(): boolean; 10 | isMSESupported(): boolean; 11 | canPlayType(mimeType: string): boolean; 12 | canPlayDrm(drmData: Array, drmConfig: PKDrmConfigObject): boolean; 13 | createAdapter(videoElement: HTMLVideoElement, source: PKMediaSourceObject, config: Object): IMediaSourceAdapter; 14 | } 15 | 16 | declare interface IMediaSourceAdapter extends FakeEventTarget { 17 | src: string; 18 | +liveDuration: number; 19 | +capabilities: PKMediaSourceCapabilities; 20 | +targetBuffer: number; 21 | load(startTime: ?number): Promise; 22 | handleMediaError(error: ?MediaError): boolean; 23 | destroy(): Promise<*>; 24 | selectVideoTrack(videoTrack: VideoTrack): void; 25 | selectAudioTrack(audioTrack: AudioTrack): void; 26 | selectTextTrack(textTrack: TextTrack): void; 27 | selectImageTrack(imageTrack: ImageTrack): void; 28 | hideTextTrack(): void; 29 | enableAdaptiveBitrate(): void; 30 | isAdaptiveBitrateEnabled(): boolean; 31 | seekToLiveEdge(): void; 32 | isLive(): boolean; 33 | isOnLiveEdge(): boolean; 34 | getStartTimeOfDvrWindow(): number; 35 | setMaxBitrate(bitrate: number): void; 36 | attachMediaSource(): void; 37 | detachMediaSource(): void; 38 | getSegmentDuration(): number; 39 | disableNativeTextTracks(): void; 40 | getDrmInfo(): ?PKDrmDataObject; 41 | } 42 | -------------------------------------------------------------------------------- /flow-typed/modules/js-logger.js: -------------------------------------------------------------------------------- 1 | declare module 'js-logger' { 2 | declare module.exports: any; 3 | } 4 | -------------------------------------------------------------------------------- /flow-typed/modules/ua-parser.js: -------------------------------------------------------------------------------- 1 | declare module 'ua-parser-js' { 2 | declare module.exports: any; 3 | } 4 | -------------------------------------------------------------------------------- /flow-typed/types/abr-config.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare type PKAbrConfigObject = { 3 | fpsDroppedMonitoringThreshold: number, 4 | fpsDroppedFramesInterval: number, 5 | capLevelOnFPSDrop: boolean 6 | }; 7 | -------------------------------------------------------------------------------- /flow-typed/types/abr-modes.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare type PKAbrModes = {[mode: string]: string}; 3 | -------------------------------------------------------------------------------- /flow-typed/types/ad-break-options.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare type PKAdBreakOptions = { 3 | type?: string, 4 | position?: number, 5 | numAds?: number 6 | }; 7 | -------------------------------------------------------------------------------- /flow-typed/types/ad-break-types.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare type PKAdBreakTypes = {[type: string]: string}; 3 | -------------------------------------------------------------------------------- /flow-typed/types/ad-options.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare type PKAdOptions = { 3 | system?: string, 4 | url?: string, 5 | contentType?: string, 6 | title?: string, 7 | position?: number, 8 | duration?: number, 9 | clickThroughUrl?: string, 10 | posterUrl?: string, 11 | skipOffset?: number, 12 | linear: boolean, 13 | width: number, 14 | height: number, 15 | bitrate: number, 16 | bumper: boolean, 17 | inStream?: boolean, 18 | vpaid?: boolean, 19 | streamId?: string, 20 | wrapperAdIds: Array, 21 | wrapperCreativeIds: Array, 22 | wrapperAdSystems: Array 23 | }; 24 | -------------------------------------------------------------------------------- /flow-typed/types/ad-tag-types.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare type PKAdTagTypes = {[type: string]: string}; 3 | -------------------------------------------------------------------------------- /flow-typed/types/auto-play-types.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare type PKAutoPlayTypes = {[type: string]: string | boolean}; 3 | -------------------------------------------------------------------------------- /flow-typed/types/cors-types.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare type PKCorsTypes = {[stream: string]: string}; 3 | -------------------------------------------------------------------------------- /flow-typed/types/custom-labels-config.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare type PKCustomLabelsConfigObject = { 3 | audio: Function, 4 | qualities: Function, 5 | video: Function 6 | }; 7 | -------------------------------------------------------------------------------- /flow-typed/types/deferred-promise.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare type DeferredPromise = Promise<*> & {resolve: Function, reject: Function}; 3 | -------------------------------------------------------------------------------- /flow-typed/types/dimensions.js: -------------------------------------------------------------------------------- 1 | declare type PKPlayerDimensions = { 2 | width: number, 3 | height: number 4 | }; 5 | 6 | declare type PKDimensionsConfig = { 7 | width?: number | string, 8 | height?: number | string, 9 | ratio?: string 10 | }; 11 | -------------------------------------------------------------------------------- /flow-typed/types/drm-config.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare type PKDrmConfigObject = { 3 | keySystem: string 4 | }; 5 | -------------------------------------------------------------------------------- /flow-typed/types/drm-data.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare type PKDrmDataObject = { 3 | licenseUrl: string, 4 | scheme: string, 5 | certificate?: string 6 | }; 7 | -------------------------------------------------------------------------------- /flow-typed/types/engine-decorator-provider.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type {IEngine} from './engine'; 3 | 4 | declare interface IEngineDecoratorProvider { 5 | getEngineDecorator(engine: IEngine, dispatchEventHandler: Function): IEngineDecorator; 6 | getName(): string; 7 | } 8 | -------------------------------------------------------------------------------- /flow-typed/types/engine-types.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare type PKEngineTypes = {[engine: string]: string}; 3 | -------------------------------------------------------------------------------- /flow-typed/types/event-types.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare type PKEventTypes = {[event: string]: string}; 3 | -------------------------------------------------------------------------------- /flow-typed/types/exteranl-thumbnails-object.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare type PKExternalThumbnailsConfig = { 3 | vttUrl: string 4 | }; 5 | -------------------------------------------------------------------------------- /flow-typed/types/external-caption-object.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare type PKExternalCaptionObject = { 3 | url: string, 4 | label: string, 5 | language: string, 6 | default: ?boolean, 7 | type: ?string 8 | }; 9 | -------------------------------------------------------------------------------- /flow-typed/types/image-player-options.js: -------------------------------------------------------------------------------- 1 | declare type ImageSourceOptions = { 2 | thumbnailAPIParams: { [parmaName: string]: string } 3 | }; 4 | -------------------------------------------------------------------------------- /flow-typed/types/log-level.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare type PKLogLevelObject = {value: number, name: string}; 3 | declare type PKLogLevels = {[level: string]: PKLogLevelObject}; 4 | declare type PKLogLevelTypes = {[level: string]: string}; 5 | declare type LogHandlerType = (messages: any[], context: Object) => void; 6 | declare type PKLogConfigObject = { 7 | level: string, 8 | handler: ?LogHandlerType 9 | }; 10 | -------------------------------------------------------------------------------- /flow-typed/types/media-source-capabilities.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare type PKMediaSourceCapabilities = { 3 | [fpsControl: string]: boolean 4 | }; 5 | -------------------------------------------------------------------------------- /flow-typed/types/media-source-options.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare type PKMediaSourceOptionsObject = { 3 | forceRedirectExternalStreams: boolean, 4 | redirectExternalStreamsHandler: ?Function, 5 | redirectExternalStreamsTimeout: ?number 6 | }; 7 | -------------------------------------------------------------------------------- /flow-typed/types/media-source.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare type PKMediaSourceObject = { 3 | mimetype: string, 4 | url: string, 5 | id?: string, 6 | bandwidth?: number, 7 | width?: number, 8 | height?: number, 9 | drmData?: Array 10 | }; 11 | -------------------------------------------------------------------------------- /flow-typed/types/media-types.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare type PKMediaTypes = {[media: string]: string}; 3 | -------------------------------------------------------------------------------- /flow-typed/types/metadata-config.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare type PKMetadataConfigObject = { 3 | name?: string, 4 | description?: string, 5 | mediaType?: string, 6 | metas?: Object, 7 | tags?: Object, 8 | epgId?: string, 9 | recordingId?: string 10 | }; 11 | -------------------------------------------------------------------------------- /flow-typed/types/mime-types.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare type PKMimeTypes = {[mime: string]: Array}; 3 | -------------------------------------------------------------------------------- /flow-typed/types/network-config.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare type PKNetworkConfigObject = { 3 | requestFilter?: Function, 4 | responseFilter?: Function, 5 | maxStaleLevelReloads: number 6 | }; 7 | -------------------------------------------------------------------------------- /flow-typed/types/play-options.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare type PKPlayOptionsObject = { 3 | programmatic: boolean 4 | }; 5 | -------------------------------------------------------------------------------- /flow-typed/types/playback-config.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare type PKPlaybackConfigObject = { 3 | audioLanguage: string, 4 | textLanguage: string, 5 | captionsDisplay: boolean, 6 | additionalAudioLanguage: string, 7 | additionalTextLanguage: string, 8 | volume: number, 9 | playsinline: boolean, 10 | crossOrigin: string, 11 | preload: string, 12 | autoplay: PKAutoPlayTypes, 13 | allowMutedAutoPlay: boolean, 14 | updateAudioDescriptionLabels: boolean, 15 | muted: boolean, 16 | pictureInPicture: boolean, 17 | streamPriority: Array, 18 | preferNative: PKPreferNativeConfigObject, 19 | inBrowserFullscreen: boolean, 20 | playAdsWithMSE: boolean, 21 | screenLockOrientionMode: string, 22 | playbackRate: number 23 | }; 24 | -------------------------------------------------------------------------------- /flow-typed/types/playback-options.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare type PKPlaybackOptionsObject = { 3 | html5: { 4 | hls: Object, 5 | dash: Object 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /flow-typed/types/player-options.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare type PKPlayerOptionsObject = { 3 | log?: PKLogConfigObject, 4 | playback?: PKPlaybackConfigObject, 5 | streaming?: PKStreamingConfigObject, 6 | session?: PKSessionConfigObject, 7 | network?: PKNetworkConfigObject, 8 | customLabels?: PKCustomLabelsConfigObject 9 | }; 10 | -------------------------------------------------------------------------------- /flow-typed/types/player-state.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import State from '../../src/state/state'; 3 | 4 | declare type Transition = { 5 | [state: string]: { 6 | [event: string]: Function 7 | } 8 | }; 9 | 10 | declare type MaybeState = State | null; 11 | 12 | declare type StateChanged = { 13 | oldState: MaybeState, 14 | newState: MaybeState 15 | }; 16 | -------------------------------------------------------------------------------- /flow-typed/types/prefer-native-config.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare type PKPreferNativeConfigObject = { 3 | hls: boolean, 4 | dash: boolean 5 | }; 6 | -------------------------------------------------------------------------------- /flow-typed/types/request-types.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare type PKRequestType = {[request: string]: number}; 3 | -------------------------------------------------------------------------------- /flow-typed/types/request.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare type PKRequestObject = { 3 | url: string, 4 | body: string | null, 5 | headers: {[header: string]: string} 6 | }; 7 | -------------------------------------------------------------------------------- /flow-typed/types/response.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare type PKResponseObject = { 3 | url: string, 4 | originalUrl: string, 5 | data: ArrayBuffer, 6 | headers: {[header: string]: string} 7 | }; 8 | -------------------------------------------------------------------------------- /flow-typed/types/restrictions-types.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare type PKABRRestrictionObject = { 3 | minHeight: number, 4 | maxHeight: number, 5 | minWidth: number, 6 | maxWidth: number, 7 | minBitrate: number, 8 | maxBitrate: number 9 | }; 10 | -------------------------------------------------------------------------------- /flow-typed/types/screen-orientation-type.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare type PKOrientationType = {[type: string]: string}; 3 | -------------------------------------------------------------------------------- /flow-typed/types/session-config.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare type PKSessionConfigObject = { 3 | id?: string, 4 | ks?: string, 5 | isAnonymous?: boolean, 6 | partnerId?: number, 7 | uiConfId?: number 8 | }; 9 | -------------------------------------------------------------------------------- /flow-typed/types/sources-config.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare type PKSourcesConfigObject = { 3 | hls: Array, 4 | dash: Array, 5 | progressive: Array, 6 | image: Array, 7 | document: Array, 8 | captions?: Array, 9 | thumbnails?: PKExternalThumbnailsConfig, 10 | options: PKMediaSourceOptionsObject, 11 | type: string, 12 | dvr: boolean, 13 | metadata: PKMetadataConfigObject, 14 | id?: string, 15 | poster?: string, 16 | duration?: number, 17 | startTime?: number, 18 | vr: ?Object, 19 | imageSourceOptions?: ImageSourceOptions, 20 | seekFrom?: number, 21 | clipTo?: number 22 | }; 23 | -------------------------------------------------------------------------------- /flow-typed/types/state-types.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare type PKStateTypes = {[state: string]: string}; 3 | -------------------------------------------------------------------------------- /flow-typed/types/stats.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare type PKStatsObject = { 3 | targetBuffer: number, 4 | availableBuffer: number 5 | }; 6 | -------------------------------------------------------------------------------- /flow-typed/types/stream-priority.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare type PKStreamPriorityObject = { 3 | engine: string, 4 | format: string 5 | }; 6 | -------------------------------------------------------------------------------- /flow-typed/types/stream-types.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare type PKStreamTypes = {[stream: string]: string}; 3 | -------------------------------------------------------------------------------- /flow-typed/types/streaming-config.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare type PKStreamingConfigObject = { 3 | forceBreakStall: boolean, 4 | lowLatencyMode: boolean, 5 | trackEmsgEvents: boolean, 6 | switchDynamicToStatic: boolean 7 | }; 8 | -------------------------------------------------------------------------------- /flow-typed/types/text-config.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import {TextStyle} from '../../src/track/text-style'; 3 | 4 | declare type PKTextConfigObject = { 5 | enableCEA708Captions: boolean, 6 | useShakaTextTrackDisplay: boolean, 7 | useNativeTextTrack: boolean, 8 | textTrackDisplaySetting: PKTextTrackDisplaySettingObject, 9 | textStyle: PKTextStyleObject, 10 | forceCenter: boolean, 11 | captionsTextTrack1Label: string, 12 | captionsTextTrack1LanguageCode: string, 13 | captionsTextTrack2Label: string, 14 | captionsTextTrack2LanguageCode: string 15 | }; 16 | -------------------------------------------------------------------------------- /flow-typed/types/text-style.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | /** 4 | * @typedef {Object} PKTextStyleObject 5 | * @property {"50%" | "75%" | "100%" | "200%" | "300%" | "400%"} fontSize='100%' - Percentage unit relative to the parent element's font size. 6 | * @property {-2 | -1 | 0 | 2 | 3 | 4} fontScale=0 - - Integer number representing the scaling factor relative to the parent element's font size. 7 | * @property {string} fontFamily='sans-serif' 8 | * @property {[number, number, number]} fontColor=[255, 255, 255] - Color in RGB format. 9 | * @property {number} fontOpacity=1 10 | * @property {Array<[number, number, number, number, number, number]>} fontEdge=[] 11 | * @property {[number, number, number]} backgroundColor=[0, 0, 0] - Color in RGB format. 12 | * @property {number} backgroundOpacity=1 13 | */ 14 | declare type PKTextStyleObject = { 15 | fontSize?: '50%' | '75%' | '100%' | '200%' | '300%' | '400%', 16 | fontScale?: -2 | -1 | 0 | 2 | 3 | 4, 17 | fontFamily?: string, 18 | fontColor?: [number, number, number], 19 | fontOpacity?: number, 20 | fontEdge?: Array<[number, number, number, number, number, number]>, 21 | backgroundColor?: [number, number, number], 22 | backgroundOpacity?: number 23 | }; 24 | -------------------------------------------------------------------------------- /flow-typed/types/text-track-cue.js: -------------------------------------------------------------------------------- 1 | declare type TextTrackCue = window.TextTrackCue & ?{ 2 | value: { 3 | key: string, 4 | data: string | Object 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /flow-typed/types/text-track-display-setting.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare type PKTextTrackDisplaySettingObject = { 3 | line: string | number, 4 | lineAlign: string, 5 | align: string, 6 | position: number, 7 | positionAlign: string, 8 | snapToLines: boolean, 9 | vertical: string, 10 | size: number 11 | }; 12 | -------------------------------------------------------------------------------- /flow-typed/types/thumbnail-vtt-cue.js: -------------------------------------------------------------------------------- 1 | declare type PKThumbnailVttCue = { 2 | startTime: number, 3 | endTime: number, 4 | imgUrl: string, 5 | coordinates: {x: number, y: number} | null, 6 | size: {height: number, width: number} | null 7 | }; 8 | -------------------------------------------------------------------------------- /flow-typed/types/track-types.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare type PKTrackTypes = {[track: string]: string}; 3 | -------------------------------------------------------------------------------- /flow-typed/types/video-dimensions.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare type PKVideoDimensionsObject = { 3 | videoHeight: number, 4 | videoWidth: number 5 | }; 6 | -------------------------------------------------------------------------------- /flow-typed/types/video-element-store.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare type PKVideoElementStore = {[id: string]: HTMLVideoElement} | {}; 3 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | const webpackConfig = require('./webpack.config')( null, {mode: 'development'}); 2 | delete webpackConfig.entry; 3 | delete webpackConfig.externals; 4 | delete webpackConfig.output; 5 | delete webpackConfig.devServer; 6 | webpackConfig.devtool = 'inline-source-map'; 7 | 8 | module.exports = function (config) { 9 | config.set({ 10 | frameworks: ['webpack', 'mocha'], 11 | browserDisconnectTimeout: 60000, 12 | browserNoActivityTimeout: 60000, 13 | files: [ 14 | 'tests/index.js', 15 | { 16 | pattern: 'tests/assets/**/*', 17 | included: false 18 | }, 19 | ], 20 | exclude: [], 21 | preprocessors: { 22 | 'tests/index.js': ['webpack', 'sourcemap'] 23 | }, 24 | reporters: ['mocha'], 25 | mochaReporter: { 26 | showDiff: true, 27 | }, 28 | 29 | coverageIstanbulReporter: { 30 | reports: ['lcov', 'text-summary'], 31 | fixWebpackSourcePaths: true 32 | }, 33 | webpack: webpackConfig, 34 | port: 9876, 35 | colors: true, 36 | logLevel: config.LOG_INFO, 37 | autoWatch: false, 38 | customLaunchers: { 39 | ChromeHeadlessWithFlags: { 40 | base: 'ChromeHeadless', 41 | flags: ['--no-sandbox', '--autoplay-policy=no-user-gesture-required', '--mute-audio', '--max-web-media-player-count=1000'] 42 | } 43 | }, 44 | browsers: ['ChromeHeadlessWithFlags'], 45 | singleRun: true, 46 | concurrency: Infinity, 47 | client: { 48 | mocha: { 49 | reporter: 'html', 50 | timeout: 50000 51 | } 52 | } 53 | }); 54 | }; 55 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@playkit-js/playkit-js", 3 | "version": "0.84.27", 4 | "keywords": [ 5 | "kaltura", 6 | "player", 7 | "html5 player" 8 | ], 9 | "homepage": "https://github.com/kaltura/playkit-js", 10 | "bugs": { 11 | "url": "https://github.com/kaltura/playkit-js/issues" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/kaltura/playkit-js" 16 | }, 17 | "license": "AGPL-3.0", 18 | "main": "./dist/playkit.js", 19 | "types": "./dist/playkit-js.d.ts", 20 | "files": [ 21 | "dist/**/*", 22 | "src/**/*", 23 | "flow-typed/**/*" 24 | ], 25 | "scripts": { 26 | "serve": "yarn run build:dev && webpack serve --open --mode development", 27 | "watch": "webpack --watch --mode development", 28 | "build:dev": "webpack --mode development", 29 | "build:prod": "webpack --mode production", 30 | "build": "yarn run build:prod && yarn run build:types", 31 | "build:types": "tsc --build tsconfig-lib.json && mkdir -p lib && api-extractor run --local --diagnostics", 32 | "type-check": "tsc --noEmit", 33 | "type-check:watch": "npm run type-check -- --watch", 34 | "test": "karma start karma.conf.js", 35 | "test:debug": "DEBUG_UNIT_TESTS=1 karma start karma.conf.js --auto-watch --no-single-run --browsers Chrome", 36 | "test:watch": "karma start karma.conf.js --auto-watch --no-single-run", 37 | "lint": "eslint src/ --ext .js --ext .ts", 38 | "lint:fix": "yarn run lint -- --fix", 39 | "prettier": "prettier --write .", 40 | "clean": "rm -rf dist/*", 41 | "precommit": "npm run build:prod && npm run type-check && npm run lint:fix", 42 | "release": "standard-version", 43 | "pushTaggedRelease": "git push --follow-tags --no-verify origin master" 44 | }, 45 | "publishConfig": { 46 | "access": "public" 47 | }, 48 | "dependencies": { 49 | "@playkit-js/webpack-common": "^1.0.3", 50 | "js-logger": "^1.6.0", 51 | "ua-parser-js": "^1.0.36" 52 | }, 53 | "devDependencies": { 54 | "@babel/core": "^7.22.20", 55 | "@babel/plugin-transform-runtime": "^7.22.15", 56 | "@babel/preset-env": "^7.22.20", 57 | "@babel/preset-typescript": "^7.22.15", 58 | "@babel/runtime": "^7.23.1", 59 | "@microsoft/api-extractor": "^7.38.0", 60 | "@playkit-js/browserslist-config": "1.0.8", 61 | "@types/chai": "^4.3.3", 62 | "@types/mocha": "^9.1.1", 63 | "@types/sinon": "^10.0.20", 64 | "@types/ua-parser-js": "^0.7.38", 65 | "@typescript-eslint/eslint-plugin": "^6.7.2", 66 | "@typescript-eslint/parser": "^6.7.2", 67 | "babel-loader": "^9.1.3", 68 | "chai": "^4.3.6", 69 | "chai-as-promised": "^7.1.1", 70 | "copy-webpack-plugin": "^11.0.0", 71 | "css-loader": "6.8.1", 72 | "eslint": "^8.56.0", 73 | "eslint-plugin-mocha": "^10.2.0", 74 | "karma": "^6.4.0", 75 | "karma-chrome-launcher": "^3.1.1", 76 | "karma-firefox-launcher": "^1.3.0", 77 | "karma-mocha": "^2.0.1", 78 | "karma-mocha-reporter": "^2.2.5", 79 | "karma-safari-launcher": "^1.0.0", 80 | "karma-sourcemap-loader": "^0.3.8", 81 | "karma-webpack": "^5.0.0", 82 | "mocha": "^10.0.0", 83 | "prettier": "^3.0.3", 84 | "sinon": "^14.0.0", 85 | "sinon-chai": "latest", 86 | "standard-version": "^9.5.0", 87 | "style-loader": "3.3.3", 88 | "typescript": "^5.2.2", 89 | "webpack": "^5.88.2", 90 | "webpack-cli": "^5.1.4", 91 | "webpack-dev-server": "^4.15.1" 92 | }, 93 | "browserslist": [ 94 | "extends @playkit-js/browserslist-config" 95 | ], 96 | "kcc": { 97 | "name": "playkit" 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/ads/ad-break-type.ts: -------------------------------------------------------------------------------- 1 | export const AdBreakType = { 2 | PRE: 'preroll', 3 | MID: 'midroll', 4 | POST: 'postroll', 5 | OVERLAY: 'overlay' 6 | } as const; 7 | -------------------------------------------------------------------------------- /src/ads/ad-event-type.ts: -------------------------------------------------------------------------------- 1 | const AdEventType = { 2 | /** 3 | * Fired when the ad can be skip by the user. 4 | */ 5 | AD_CAN_SKIP: 'adcanskip', 6 | /** 7 | * Fired when the ad manifest has been loaded. 8 | */ 9 | AD_MANIFEST_LOADED: 'admanifestloaded', 10 | /** 11 | * Fired when ad data is available. 12 | */ 13 | AD_LOADED: 'adloaded', 14 | /** 15 | * Fired when the ad starts playing. 16 | */ 17 | AD_STARTED: 'adstarted', 18 | /** 19 | * Fired when the ad is resumed. 20 | */ 21 | AD_RESUMED: 'adresumed', 22 | /** 23 | * Fired when the ad is paused. 24 | */ 25 | AD_PAUSED: 'adpaused', 26 | /** 27 | * Fired when the ad is clicked. 28 | */ 29 | AD_CLICKED: 'adclicked', 30 | /** 31 | * Fired when the ad is skipped by the user. 32 | */ 33 | AD_SKIPPED: 'adskipped', 34 | /** 35 | * Fired when the ad completes playing. 36 | */ 37 | AD_COMPLETED: 'adcompleted', 38 | /** 39 | * Fired when an error occurred while the ad was loading or playing. 40 | */ 41 | AD_ERROR: 'aderror', 42 | /** 43 | * Fired when the ads plugin is done playing all own ads. 44 | */ 45 | ADS_COMPLETED: 'adscompleted', 46 | /** 47 | * Fired when the ads manager is done playing all the ads. 48 | */ 49 | ALL_ADS_COMPLETED: 'alladscompleted', 50 | /** 51 | * Fired when content should be paused. This usually happens right before an ad is about to cover the content. 52 | */ 53 | AD_BREAK_START: 'adbreakstart', 54 | /** 55 | * Fired when content should be resumed. This usually happens when an ad finishes or collapses. 56 | */ 57 | AD_BREAK_END: 'adbreakend', 58 | /** 59 | * Fired when the ad playhead crosses first quartile. 60 | */ 61 | AD_FIRST_QUARTILE: 'adfirstquartile', 62 | /** 63 | * Fired when the ad playhead crosses midpoint. 64 | */ 65 | AD_MIDPOINT: 'admidpoint', 66 | /** 67 | * Fired when the ad playhead crosses third quartile. 68 | */ 69 | AD_THIRD_QUARTILE: 'adthirdquartile', 70 | /** 71 | * Fired when the ad is closed by the user. 72 | */ 73 | USER_CLOSED_AD: 'userclosedad', 74 | /** 75 | * Fired when the ad volume has changed. 76 | */ 77 | AD_VOLUME_CHANGED: 'advolumechanged', 78 | /** 79 | * Fired when the ad volume has been muted. 80 | */ 81 | AD_MUTED: 'admuted', 82 | /** 83 | * Fired on ad time progress. 84 | */ 85 | AD_PROGRESS: 'adprogress', 86 | /** 87 | * Fired when the ad has stalled playback to buffer. 88 | */ 89 | AD_BUFFERING: 'adbuffering', 90 | /** 91 | * Fired when an ad waterfalling occurred 92 | */ 93 | AD_WATERFALLING: 'adwaterfalling', 94 | /** 95 | * Fired when an ad waterfalling failed 96 | */ 97 | AD_WATERFALLING_FAILED: 'adwaterfallingfailed', 98 | /** 99 | * Fires when browser fails to autoplay an ad. 100 | */ 101 | AD_AUTOPLAY_FAILED: 'adautoplayfailed' 102 | } as const; 103 | 104 | export {AdEventType}; 105 | -------------------------------------------------------------------------------- /src/ads/ad-tag-type.ts: -------------------------------------------------------------------------------- 1 | export const AdTagType = { 2 | VAST: 'vast', 3 | VMAP: 'vmap' 4 | } as const; 5 | -------------------------------------------------------------------------------- /src/assets/encoding-sources.json: -------------------------------------------------------------------------------- 1 | { 2 | "Base64Mp4Source": "data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=" 3 | } 4 | -------------------------------------------------------------------------------- /src/assets/style.css: -------------------------------------------------------------------------------- 1 | .playkit-container { 2 | position: relative; 3 | width: 100%; 4 | height: 100%; 5 | color: #fff; 6 | outline: none; 7 | -webkit-touch-callout: none; 8 | -webkit-user-select: none; 9 | -moz-user-select: none; 10 | -ms-user-select: none; 11 | user-select: none; 12 | -webkit-tap-highlight-color: transparent; 13 | } 14 | 15 | .playkit-engine { 16 | width: 100%; 17 | height: 100%; 18 | position: absolute; 19 | top: 0; 20 | left: 0; 21 | object-fit: contain; 22 | } 23 | 24 | .playkit-engine video::-webkit-media-controls-panel, 25 | .playkit-engine video::-webkit-media-controls-panel-container, 26 | .playkit-engine video::-webkit-media-controls-start-playback-button, 27 | .playkit-engine video::-webkit-media-controls-play-button { 28 | display: none; 29 | -webkit-appearance: none; 30 | } 31 | 32 | .playkit-poster { 33 | position: absolute; 34 | display: block; 35 | top: 0; 36 | bottom: 0; 37 | left: 0; 38 | right: 0; 39 | background-size: contain; 40 | background-position: center center; 41 | background-repeat: no-repeat; 42 | background-color: var(--playkit-player-background-color); 43 | pointer-events: none; 44 | } 45 | 46 | .playkit-subtitles { 47 | position: absolute; 48 | top: 0; 49 | bottom: 0; 50 | right: 0; 51 | left: 0; 52 | pointer-events: none; 53 | margin-bottom: 5px; 54 | } 55 | 56 | .playkit-black-cover { 57 | position: absolute; 58 | width: 100%; 59 | height: 100%; 60 | background-color: black; 61 | pointer-events: none; 62 | } 63 | 64 | .playkit-size-iframe { 65 | width: 100%; 66 | height: 100%; 67 | position: absolute; 68 | border: 0; 69 | z-index: -100; 70 | } 71 | 72 | .playkit-in-browser-fullscreen-mode { 73 | width: 100% !important; 74 | height: 100% !important; 75 | position: fixed !important; 76 | top: 0 !important; 77 | left: 0 !important; 78 | /*added for blocking element with fixed position which could be on the top of the player */ 79 | z-index: 999999 !important; 80 | } 81 | -------------------------------------------------------------------------------- /src/drm/drm-scheme.ts: -------------------------------------------------------------------------------- 1 | export const DrmScheme = { 2 | WIDEVINE: 'com.widevine.alpha', 3 | PLAYREADY: 'com.microsoft.playready', 4 | FAIRPLAY: 'com.apple.fairplay' 5 | }; 6 | -------------------------------------------------------------------------------- /src/drm/fairplay.ts: -------------------------------------------------------------------------------- 1 | import {FairPlayDrmConfigType} from '../engines/html5/media-source/adapters/fairplay-drm-handler'; 2 | import {DrmScheme} from './drm-scheme'; 3 | import {PKDrmConfigObject, PKDrmDataObject} from '../types'; 4 | import {ILogger} from 'js-logger'; 5 | import getLogger from '../utils/logger'; 6 | 7 | const _logger: ILogger = getLogger('FairPlay'); 8 | 9 | class FairPlay { 10 | /** 11 | * FairPlay is the configure key system. 12 | * @param {Array} drmData - The drm data. 13 | * @param {PKDrmConfigObject} drmConfig - The drm config. 14 | * @return {boolean} - Whether FairPlay is the configure key system. 15 | */ 16 | public static isConfigured(drmData: Array, drmConfig: PKDrmConfigObject): boolean { 17 | return DrmScheme.FAIRPLAY === drmConfig.keySystem && !!drmData.find(drmEntry => drmEntry.scheme === drmConfig.keySystem); 18 | } 19 | 20 | /** 21 | * FairPlay playback supports in case 2 conditions are met: 22 | * 1. The environment supports FairPlay playback. 23 | * 2. The drm data of the source object contains entry with FairPlay scheme. 24 | * @param {Array} drmData - The drm data to check. 25 | * @return {boolean} - Whether FairPlay can be play on the current environment. 26 | */ 27 | public static canPlayDrm(drmData: Array): boolean { 28 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 29 | // @ts-ignore 30 | const result = drmData.some(drmEntry => drmEntry.scheme === DrmScheme.FAIRPLAY) && !!window.WebKitMediaKeys; 31 | _logger.debug(`Can play DRM scheme of: ${DrmScheme.FAIRPLAY} is ${result.toString()}`); 32 | return result; 33 | } 34 | 35 | /** 36 | * Sets the FairPlay playback. 37 | * @param {FairPlayDrmConfigType} config - The config to manipulate. 38 | * @param {Array} drmData - The drm data. 39 | * @returns {void} 40 | */ 41 | public static setDrmPlayback(config: FairPlayDrmConfigType, drmData: Array): void { 42 | _logger.debug('Sets drm playback'); 43 | const drmEntry = drmData.find(drmEntry => drmEntry.scheme === DrmScheme.FAIRPLAY); 44 | if (drmEntry) { 45 | config.licenseUrl = drmEntry.licenseUrl; 46 | config.certificate = drmEntry.certificate; 47 | } 48 | } 49 | } 50 | 51 | export default FairPlay; 52 | -------------------------------------------------------------------------------- /src/engines/engine-decorator-manager.ts: -------------------------------------------------------------------------------- 1 | // @flow 2 | import getLogger from '../utils/logger'; 3 | import {IEngine} from '../types/interfaces/engine'; 4 | import {IEngineDecorator} from '../types/interfaces/engine-decorator'; 5 | import {IEngineDecoratorProvider} from '../types'; 6 | import { FakeEvent } from '../event/fake-event'; 7 | 8 | /** 9 | * Engine decorator manager for plugins. 10 | * @class EngineDecoratorManager 11 | */ 12 | class EngineDecoratorManager { 13 | private _decoratorProviders: Map = new Map(); 14 | private _logger: any = getLogger('EngineDecoratorManager'); 15 | 16 | public register(engineDecoratorProvider: IEngineDecoratorProvider): void { 17 | if (!this._decoratorProviders.has(engineDecoratorProvider.getName())) { 18 | this._decoratorProviders.set(engineDecoratorProvider.getName(), engineDecoratorProvider); 19 | } else { 20 | this._logger.warn(`decorator already registered for ${engineDecoratorProvider.getName()}`); 21 | } 22 | } 23 | 24 | public createDecorators(engine: IEngine, dispatchEvent: (event: FakeEvent) => void): Array { 25 | this._logger.debug(`decorators created for ${Array.from(this._decoratorProviders.keys()).toString()}`); 26 | return Array.from(this._decoratorProviders.values(), engineDecoratorProvider => 27 | engineDecoratorProvider.getEngineDecorator(engine, dispatchEvent) 28 | ); 29 | } 30 | 31 | public destroy(): void { 32 | this._logger.debug('decorators destroyed'); 33 | this._decoratorProviders.clear(); 34 | } 35 | } 36 | 37 | export {EngineDecoratorManager}; 38 | -------------------------------------------------------------------------------- /src/engines/engine-decorator-provider.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Engine decorator provider. 3 | * @class EngineDecoratorProvider 4 | * @param {IEngineDecoratorProvider} plugin - The plugin which have implemented decorator. 5 | * @implements {IEngineDecorator} 6 | */ 7 | import {IEngineDecoratorProvider} from '../types'; 8 | import {IEngine} from '../types/interfaces/engine'; 9 | import {IEngineDecorator} from '../types/interfaces/engine-decorator'; 10 | import { FakeEvent } from '../event/fake-event'; 11 | 12 | class EngineDecoratorProvider implements IEngineDecoratorProvider { 13 | private _name: string; 14 | private _getEngineDecorator: (engine: IEngine, dispatchEventHandler: (event: FakeEvent) => void) => IEngineDecorator; 15 | 16 | constructor(plugin: IEngineDecoratorProvider) { 17 | this._name = plugin.getName(); 18 | this._getEngineDecorator = plugin.getEngineDecorator.bind(plugin); 19 | } 20 | 21 | public getEngineDecorator(engine: IEngine, dispatchEventHandler: (event: FakeEvent) => void): IEngineDecorator { 22 | return this._getEngineDecorator(engine, dispatchEventHandler); 23 | } 24 | 25 | public getName(): string { 26 | return this._name; 27 | } 28 | } 29 | 30 | export {EngineDecoratorProvider}; 31 | -------------------------------------------------------------------------------- /src/engines/engine-decorator.ts: -------------------------------------------------------------------------------- 1 | import { FakeEvent } from '../event/fake-event'; 2 | import {EventType} from '../event/event-type'; 3 | import { EventManager } from '../event/event-manager'; 4 | import { FakeEventTarget } from '../event/fake-event-target'; 5 | import {EngineDecoratorManager} from './engine-decorator-manager'; 6 | import {IEngineDecorator} from '../types/interfaces/engine-decorator'; 7 | import {IEngine} from '../types/interfaces/engine'; 8 | 9 | export type EngineDecoratorType = EngineDecorator & IEngine; 10 | 11 | /** 12 | * Engine decorator for plugin. 13 | * @class EngineDecorator 14 | * @param {IEngineDecorator} engine - The engine to decorate. 15 | * @implements {IEngineDecorator} 16 | */ 17 | class EngineDecorator extends FakeEventTarget implements IEngineDecorator { 18 | private _pluginDecorators: Array; 19 | private _eventManager: EventManager; 20 | 21 | constructor(engine: IEngine, decoratorManager: EngineDecoratorManager) { 22 | super(); 23 | this._eventManager = new EventManager(); 24 | this._pluginDecorators = decoratorManager.createDecorators(engine, super.dispatchEvent.bind(this)); 25 | const events: Array = Object.values(EventType); 26 | events.forEach(event => this._eventManager.listen(engine, event, (e: FakeEvent) => this.dispatchEvent(e))); 27 | return new Proxy(engine, { 28 | get: (obj, prop): IEngine => { 29 | if (prop === 'destroy') { 30 | this._destroy(); 31 | } 32 | const activeDecorator = this._pluginDecorators.find(decorator => decorator.active); 33 | let target; 34 | //For events the proxy is the target - to avoid listening to engine itself 35 | if (prop === 'addEventListener' || prop === 'removeEventListener') { 36 | // eslint-disable-next-line 37 | target = this; 38 | } else { 39 | target = activeDecorator && prop in activeDecorator ? activeDecorator : obj; 40 | } 41 | return target[prop] && typeof target[prop].bind === 'function' ? target[prop].bind(target) : target[prop]; 42 | }, 43 | set: (obj, prop, value): boolean => { 44 | const activeDecorator = this._pluginDecorators.find(decorator => prop in decorator && decorator.active); 45 | activeDecorator && prop in activeDecorator ? (activeDecorator[prop] = value) : (obj[prop] = value); 46 | return true; 47 | } 48 | }) as EngineDecoratorType; 49 | } 50 | 51 | public dispatchEvent(event: FakeEvent): boolean { 52 | const activeDecorator = this._pluginDecorators.find(decorator => decorator.active); 53 | return activeDecorator && activeDecorator.dispatchEvent ? activeDecorator.dispatchEvent(event) : super.dispatchEvent(event); 54 | } 55 | 56 | private _destroy(): void { 57 | this._pluginDecorators = []; 58 | this._eventManager.destroy(); 59 | } 60 | 61 | public get active(): boolean { 62 | return true; 63 | } 64 | } 65 | 66 | export {EngineDecorator}; 67 | -------------------------------------------------------------------------------- /src/engines/engine-provider.ts: -------------------------------------------------------------------------------- 1 | import getLogger from '../utils/logger'; 2 | import Html5 from './html5/html5'; 3 | import {IEngineStatic} from '../types'; 4 | 5 | /** 6 | * Engine Provider 7 | * @classdesc 8 | */ 9 | class EngineProvider { 10 | /** 11 | * The logger of the Engine provider. 12 | * @member {any} _logger 13 | * @static 14 | * @private 15 | */ 16 | private static _logger: any = getLogger('EngineProvider'); 17 | 18 | /** 19 | * The Engine registry. 20 | * @member {Object} _engineProviders 21 | * @static 22 | * @private 23 | */ 24 | private static _engineProviders: {[id: string]: IEngineStatic} = {}; 25 | 26 | /** 27 | * Add an engine to the registry. 28 | * @function register 29 | * @param {string} id - The engine id. 30 | * @param {IEngineStatic} engine - The engine to register. 31 | * @static 32 | * @returns {void} 33 | */ 34 | public static register(id: string, engine: IEngineStatic): void { 35 | if (id && !EngineProvider._engineProviders[id]) { 36 | EngineProvider._logger.debug(`Engine <${id}> has been registered successfully`); 37 | EngineProvider._engineProviders[id] = engine; 38 | } else { 39 | EngineProvider._logger.debug(`Engine <${id}> is already registered, do not register again`); 40 | } 41 | } 42 | 43 | /** 44 | * Remove an engine from the registry. 45 | * @function unRegister 46 | * @param {string} id - The engine id. 47 | * @static 48 | * @returns {void} 49 | */ 50 | public static unRegister(id: string): void { 51 | if (EngineProvider._engineProviders[id]) { 52 | EngineProvider._logger.debug(`Unregistered <${id}> Engine`); 53 | delete EngineProvider._engineProviders[id]; 54 | } 55 | } 56 | 57 | /** 58 | * Get the appropriate Engines. 59 | * @function getEngines 60 | * @returns {Array} - The Array of engines, or null if such doesn't exists. 61 | * @static 62 | */ 63 | public static getEngines(): Array { 64 | return Object.keys(EngineProvider._engineProviders).map(key => EngineProvider._engineProviders[key]); 65 | } 66 | 67 | /** 68 | * Destroys and clear the registered engines 69 | * @static 70 | * @returns {void} 71 | */ 72 | public static destroy(): void { 73 | EngineProvider._engineProviders = {}; 74 | } 75 | } 76 | 77 | if (Html5.isSupported()) { 78 | EngineProvider.register(Html5.id, Html5); 79 | } 80 | 81 | const registerEngine = EngineProvider.register; 82 | const unRegisterEngine = EngineProvider.unRegister; 83 | export {registerEngine, unRegisterEngine, EngineProvider}; 84 | -------------------------------------------------------------------------------- /src/engines/engine-type.ts: -------------------------------------------------------------------------------- 1 | export const EngineType = { 2 | HTML5: 'html5', 3 | FLASH: 'flash', 4 | SILVERLIGHT: 'silverlight', 5 | CAST: 'cast', 6 | YOUTUBE: 'youtube', 7 | IMAGE: 'image', 8 | DOCUMENT: 'document' 9 | } as const; 10 | -------------------------------------------------------------------------------- /src/engines/html5/cors-types.ts: -------------------------------------------------------------------------------- 1 | export const CorsType = { 2 | ANONYMOUS: 'anonymous', 3 | USE_CREDENTIALS: 'use-credentials' 4 | } as const; 5 | -------------------------------------------------------------------------------- /src/engines/html5/media-source/adapters/native-adapter-default-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "heartbeatTimeout": 30000 3 | } 4 | -------------------------------------------------------------------------------- /src/engines/stream-type.ts: -------------------------------------------------------------------------------- 1 | export const StreamType = { 2 | DASH: 'dash', 3 | HLS: 'hls', 4 | PROGRESSIVE: 'progressive', 5 | IMAGE: 'image', 6 | DOCUMENT: 'document' 7 | } as const; 8 | -------------------------------------------------------------------------------- /src/enums/auto-play-type.ts: -------------------------------------------------------------------------------- 1 | import {PKAutoPlayTypes} from '../types/auto-play-types'; 2 | 3 | const AutoPlayType: PKAutoPlayTypes = { 4 | TRUE: true, 5 | FALSE: false, 6 | IN_VIEW: 'inview' 7 | }; 8 | 9 | export {AutoPlayType}; 10 | -------------------------------------------------------------------------------- /src/enums/media-type.ts: -------------------------------------------------------------------------------- 1 | import {PKMediaTypes} from '../types/media-types'; 2 | 3 | const MediaType: PKMediaTypes = { 4 | VOD: 'Vod', 5 | LIVE: 'Live', 6 | AUDIO: 'Audio', 7 | IMAGE: 'Image', 8 | DOCUMENT: 'Document', 9 | UNKNOWN: 'Unknown' 10 | }; 11 | export {MediaType}; 12 | -------------------------------------------------------------------------------- /src/enums/mime-type.ts: -------------------------------------------------------------------------------- 1 | import {PKMimeTypes} from '../types'; 2 | 3 | const MimeType: PKMimeTypes = { 4 | HLS: ['application/x-mpegurl', 'application/vnd.apple.mpegurl'], 5 | DASH: ['application/dash+xml'], 6 | PROGRESSIVE: ['video/mp4'], 7 | SMOOTH_STREAMING: ['application/vnd.ms-sstr+xml'] 8 | }; 9 | 10 | export {MimeType}; 11 | -------------------------------------------------------------------------------- /src/enums/request-type.ts: -------------------------------------------------------------------------------- 1 | import {PKRequestType} from '../types'; 2 | 3 | const RequestType: PKRequestType = { 4 | MANIFEST: 0, 5 | SEGMENT: 1, 6 | LICENSE: 2 7 | }; 8 | 9 | export {RequestType}; 10 | -------------------------------------------------------------------------------- /src/enums/screen-orientation-type.ts: -------------------------------------------------------------------------------- 1 | import {PKOrientationType} from '../types'; 2 | 3 | const ScreenOrientationType: PKOrientationType = { 4 | NONE: 'none', 5 | ANY: 'any', 6 | NATURAL: 'natural', 7 | LANDSCAPE: 'landscape', 8 | PORTRAIT: 'portrait', 9 | PORTRAIT_PRIMARY: 'portrait-primary', 10 | PORTRAIT_SECONDARY: 'portrait-secondary', 11 | LANDSCAPE_PRIMARY: 'landscape-primary', 12 | LANDSCAPE_SECONDARY: 'landscape-secondary' 13 | }; 14 | 15 | export {ScreenOrientationType}; 16 | -------------------------------------------------------------------------------- /src/error/category.ts: -------------------------------------------------------------------------------- 1 | type CategoryType = {[category: string]: number}; 2 | 3 | const Category: CategoryType = { 4 | /** Errors from the network stack. */ 5 | NETWORK: 1, 6 | 7 | /** Errors parsing text streams. */ 8 | TEXT: 2, 9 | 10 | /** Errors parsing or processing audio or video streams. */ 11 | MEDIA: 3, 12 | 13 | /** Errors parsing the Manifest. */ 14 | MANIFEST: 4, 15 | 16 | /** Errors related to streaming. */ 17 | STREAMING: 5, 18 | 19 | /** Errors related to DRM. */ 20 | DRM: 6, 21 | 22 | /** Miscellaneous errors from the player. */ 23 | PLAYER: 7, 24 | 25 | /** Errors related to ads. */ 26 | ADS: 8, 27 | 28 | /** Errors in the database storage (offline). */ 29 | STORAGE: 9, 30 | 31 | /** Errors related to cast. */ 32 | CAST: 10, 33 | 34 | /** Errors from VR plugin. */ 35 | VR: 11, 36 | 37 | /** Media is not ready error. */ 38 | MEDIA_NOT_READY: 12, 39 | 40 | /** Geolocation restriction error. */ 41 | GEO_LOCATION: 13, 42 | 43 | /** Media is unavailable error. */ 44 | MEDIA_UNAVAILABLE: 14, 45 | 46 | /** IP restriction error. */ 47 | IP_RESTRICTED: 15, 48 | 49 | /** Scheduled restriction error. */ 50 | SITE_RESTRICTED: 16, 51 | 52 | /** Scheduled restriction error. */ 53 | SCHEDULED_RESTRICTED: 17, 54 | 55 | /** Access Control error */ 56 | 57 | ACCESS_CONTROL_BLOCKED: 18 58 | }; 59 | 60 | export {Category}; 61 | export type {CategoryType}; 62 | -------------------------------------------------------------------------------- /src/error/error.ts: -------------------------------------------------------------------------------- 1 | import getLogger, {getLogLevel, LogLevel} from '../utils/logger'; 2 | import type {SeverityType} from './severity'; 3 | import {Severity} from './severity'; 4 | import type {CodeType} from './code'; 5 | import {Code} from './code'; 6 | import type {CategoryType} from './category'; 7 | import {Category} from './category'; 8 | 9 | const CLASS_NAME: string = 'Error'; 10 | 11 | /** 12 | * @classdesc This is a description of the error class. 13 | */ 14 | export default class Error { 15 | public severity: number; 16 | public category: number; 17 | public code: number; 18 | public data: any; 19 | public errorDetails: any; 20 | /** 21 | * @enum {number} 22 | */ 23 | public static Severity: SeverityType = Severity; 24 | /** 25 | * @enum {number} 26 | */ 27 | public static Category: CategoryType = Category; 28 | /** 29 | * @enum {number} 30 | */ 31 | public static Code: CodeType = Code; 32 | private static _logger: any = getLogger(CLASS_NAME); 33 | 34 | /** 35 | * @constructor 36 | * @param {number} severity - error's severity 37 | * @param {number} category - error's category. 38 | * @param {number} code - error's code. 39 | * @param {any} data - additional data for the error. 40 | * @param {any} errorDetails - error details including title and message - optional. 41 | */ 42 | constructor(severity: number, category: number, code: number, data: any = {}, errorDetails?: any) { 43 | this.severity = severity; 44 | this.category = category; 45 | this.code = code; 46 | this.data = data; 47 | this.errorDetails = errorDetails || {}; 48 | if (getLogLevel(CLASS_NAME) !== LogLevel.OFF) { 49 | Error._logger.error(`Category:${category} | Code:${code} |`, data); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/error/severity.ts: -------------------------------------------------------------------------------- 1 | type SeverityType = {[severity: string]: number}; 2 | 3 | const Severity: SeverityType = { 4 | /** 5 | * An error occurred, but the Player is attempting to recover from the error. 6 | * 7 | * If the Player cannot ultimately recover, it still may not throw a CRITICAL 8 | * error. For example, retrying for a media segment will never result in 9 | * a CRITICAL error (the Player will just retry forever). 10 | */ 11 | RECOVERABLE: 1, 12 | /** 13 | * A critical error that the library cannot recover from. These usually cause 14 | * the Player to stop loading or updating. A new manifest must be loaded 15 | * to reset the library. 16 | */ 17 | CRITICAL: 2 18 | }; 19 | 20 | export {Severity}; 21 | export type {SeverityType}; 22 | -------------------------------------------------------------------------------- /src/event/fake-event.ts: -------------------------------------------------------------------------------- 1 | //@flow 2 | /** 3 | * Create an Event work-alike object based on the dictionary. 4 | * The event should contain all of the same properties from the dict. 5 | * @param {string} type - 6 | * @param {Object=} opt_dict - 7 | * @constructor 8 | * @extends {Event} 9 | */ 10 | export class FakeEvent { 11 | /** @const {boolean} */ 12 | public bubbles: boolean; 13 | 14 | /** @const {boolean} */ 15 | public cancelable: boolean; 16 | 17 | /** @const {boolean} */ 18 | public defaultPrevented: boolean; 19 | 20 | /** 21 | * According to MDN, Chrome uses high-res timers instead of epoch time. 22 | * Follow suit so that timeStamps on FakeEvents use the same base as 23 | * on native Events. 24 | * @const {number} 25 | * @see https://developer.mozilla.org/en-US/docs/Web/API/Event/timeStamp 26 | */ 27 | public timeStamp: number | Date; 28 | 29 | /** @const {string} */ 30 | public type: string; 31 | 32 | /** @const {boolean} */ 33 | public isTrusted: boolean; 34 | 35 | /** @type {EventTarget} */ 36 | public currentTarget: any; 37 | 38 | /** @type {EventTarget} */ 39 | public target: any; 40 | 41 | /** 42 | * Non-standard property read by FakeEventTarget to stop processing listeners. 43 | * @type {boolean} 44 | */ 45 | public stopped: boolean; 46 | 47 | public payload: any; 48 | 49 | constructor(type: string, payload?: any) { 50 | // These Properties below cannot be set by dict. They are all provided for 51 | // compatibility with native events. 52 | 53 | /** @const {boolean} */ 54 | this.bubbles = false; 55 | 56 | /** @const {boolean} */ 57 | this.cancelable = false; 58 | 59 | /** @const {boolean} */ 60 | this.defaultPrevented = false; 61 | 62 | /** 63 | * According to MDN, Chrome uses high-res timers instead of epoch time. 64 | * Follow suit so that timeStamps on FakeEvents use the same base as 65 | * on native Events. 66 | * @const {number} 67 | * @see https://developer.mozilla.org/en-US/docs/Web/API/Event/timeStamp 68 | */ 69 | this.timeStamp = window.performance ? window.performance.now() : Date.now(); 70 | 71 | /** @const {string} */ 72 | this.type = type; 73 | 74 | /** @const {boolean} */ 75 | this.isTrusted = false; 76 | 77 | /** @type {EventTarget} */ 78 | this.currentTarget = null; 79 | 80 | /** @type {EventTarget} */ 81 | this.target = null; 82 | 83 | /** 84 | * Non-standard property read by FakeEventTarget to stop processing listeners. 85 | * @type {boolean} 86 | */ 87 | this.stopped = false; 88 | 89 | this.payload = payload; 90 | } 91 | 92 | /** 93 | * Does nothing, since FakeEvents have no default. Provided for compatibility 94 | * with native Events. 95 | * @override 96 | */ 97 | public preventDefault(): void {} 98 | 99 | /** 100 | * Stops processing event listeners for this event. Provided for compatibility 101 | * with native Events. 102 | * @override 103 | */ 104 | public stopImmediatePropagation(): void { 105 | this.stopped = true; 106 | } 107 | 108 | /** 109 | * Does nothing, since FakeEvents do not bubble. Provided for compatibility 110 | * with native Events. 111 | * @override 112 | */ 113 | public stopPropagation(): void {} 114 | } 115 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Title 6 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /src/middleware/base-middleware.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Base middleware. 3 | * @classdesc 4 | */ 5 | export default class BaseMiddleware { 6 | /** 7 | * Id of the middleware instance. 8 | * @public 9 | */ 10 | public id!: string; 11 | 12 | /** 13 | * Calls the next handler in the middleware chain. 14 | * @param {Function} next - The next handler in the middleware chain. 15 | * @returns {void} 16 | */ 17 | public callNext(next: (...args: any[]) => any): void { 18 | if (next) { 19 | next(); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/middleware/middleware.ts: -------------------------------------------------------------------------------- 1 | import {MultiMap} from '../utils/multi-map'; 2 | import BaseMiddleware from './base-middleware'; 3 | import getLogger from '../utils/logger'; 4 | 5 | /** 6 | * Generic middleware implementation. 7 | */ 8 | export default class Middleware { 9 | /** 10 | * The registered middlewares. 11 | * @private 12 | * @member 13 | */ 14 | private _middlewares: MultiMap; 15 | /** 16 | * The actions supported by the middleware. 17 | * @private 18 | * @member 19 | */ 20 | private _actions: {[action: string]: string}; 21 | /** 22 | * The logger of the middleware. 23 | * @private 24 | * @member 25 | */ 26 | private _logger: any; 27 | 28 | /** 29 | * @constructor 30 | * @param {Object} actions - The actions for the middleware. 31 | */ 32 | constructor(actions: {[action: string]: string}) { 33 | this._actions = actions; 34 | this._middlewares = new MultiMap(); 35 | this._logger = getLogger('Middleware'); 36 | } 37 | 38 | /** 39 | * Registers a middleware instance to the middleware chain. 40 | * @param {BaseMiddleware} middlewareInstance - The middleware instance. 41 | * @public 42 | * @returns {void} 43 | */ 44 | public use(middlewareInstance: BaseMiddleware): void { 45 | for (const action in this._actions) { 46 | const apiAction = this._actions[action]; 47 | // $FlowFixMe 48 | if (typeof middlewareInstance[apiAction] === 'function') { 49 | this._logger.debug(`Register <${middlewareInstance.id}> for action ${apiAction}`); 50 | // $FlowFixMe 51 | this._middlewares.push(apiAction, middlewareInstance[apiAction].bind(middlewareInstance)); 52 | } 53 | } 54 | } 55 | 56 | /** 57 | * Runs a middleware chain for a specific action. 58 | * @param {string} action - The action to run. 59 | * @param {Function} callback - The callback function. 60 | * @param {Array} params - The action params. 61 | * @public 62 | * @returns {void} 63 | */ 64 | public run(action: string, callback: (...args: any[]) => any, ...params: Array): void { 65 | this._logger.debug('Start middleware chain for action ' + action); 66 | const middlewares = this._middlewares.get(action); 67 | this._executeMiddleware( 68 | middlewares, 69 | (...params) => { 70 | this._logger.debug('Finish middleware chain for action ' + action); 71 | callback(...params); 72 | }, 73 | params 74 | ); 75 | } 76 | 77 | /** 78 | * Executes all the middlewares one by one. 79 | * @param {Array} middlewares - The middlewares for a specific action. 80 | * @param {Function} callback - The callback function. 81 | * @param {Array} origParams - The original action params. 82 | * @private 83 | * @returns {void} 84 | */ 85 | private _executeMiddleware(middlewares: Array<(...args: any[]) => any>, callback: (...args: any[]) => any, origParams: Array): void { 86 | let params = origParams; 87 | const applyFunc = (fn, prevParams, next?): void => { 88 | if (prevParams?.length) { 89 | params = prevParams; 90 | } 91 | params?.length ? fn(...params, next) : fn(next); 92 | }; 93 | const composition = middlewares.reduceRight( 94 | (next, fn) => (...prevParams) => { 95 | applyFunc(fn, prevParams, next); 96 | }, 97 | (...prevParams) => { 98 | applyFunc(callback, prevParams); 99 | } 100 | ); 101 | composition(); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/middleware/playback-middleware.ts: -------------------------------------------------------------------------------- 1 | import Middleware from './middleware'; 2 | import BaseMiddleware from './base-middleware'; 3 | 4 | /** 5 | * The playback middleware. 6 | */ 7 | export default class PlaybackMiddleware { 8 | /** 9 | * The actions of the playback middleware. 10 | * @static 11 | */ 12 | public static Actions: {[action: string]: string} = { 13 | LOAD: 'load', 14 | PLAY: 'play', 15 | PAUSE: 'pause', 16 | SET_CURRENT_TIME: 'setCurrentTime' 17 | }; 18 | /** 19 | * The middleware implementation. 20 | * @private 21 | * @member 22 | */ 23 | private _middleware: Middleware; 24 | 25 | /** 26 | * @constructor 27 | */ 28 | constructor() { 29 | this._middleware = new Middleware(PlaybackMiddleware.Actions); 30 | } 31 | 32 | /** 33 | * Registers a playback middleware instance to the middleware chain. 34 | * @param {BaseMiddleware} middlewareInstance - The middleware instance. 35 | * @public 36 | * @returns {void} 37 | */ 38 | public use(middlewareInstance: BaseMiddleware): void { 39 | this._middleware.use(middlewareInstance); 40 | } 41 | 42 | /** 43 | * Runs a load chain. 44 | * @param {Function} callback - The last load handler in the chain. 45 | * @public 46 | * @returns {void} 47 | */ 48 | public load(callback: () => void): void { 49 | this._middleware.run(PlaybackMiddleware.Actions.LOAD, callback); 50 | } 51 | 52 | /** 53 | * Runs a play chain. 54 | * @param {Function} callback - The last play handler in the chain. 55 | * @public 56 | * @returns {void} 57 | */ 58 | public play(callback: () => any): void { 59 | this._middleware.run(PlaybackMiddleware.Actions.PLAY, callback); 60 | } 61 | 62 | /** 63 | * Runs a pause chain. 64 | * @param {Function} callback - The last pause handler in the chain. 65 | * @public 66 | * @returns {void} 67 | */ 68 | public pause(callback: () => any): void { 69 | this._middleware.run(PlaybackMiddleware.Actions.PAUSE, callback); 70 | } 71 | 72 | /** 73 | * Runs a setCurrentTime chain. 74 | * @param {Number} to - The number to set in seconds. 75 | * @param {Function} callback - The last setCurrentTime handler in the chain. 76 | * @public 77 | * @returns {void} 78 | */ 79 | public setCurrentTime(to: number, callback: () => any): void { 80 | this._middleware.run(PlaybackMiddleware.Actions.SET_CURRENT_TIME, callback, to); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/player-config.js: -------------------------------------------------------------------------------- 1 | import {ScreenOrientationType} from './enums/screen-orientation-type'; 2 | 3 | const DefaultConfig = { 4 | log: { 5 | level: 'ERROR' 6 | }, 7 | text: { 8 | enableCEA708Captions: true, 9 | useNativeTextTrack: false, 10 | forceCenter: false, 11 | captionsTextTrack1Label: 'English', 12 | captionsTextTrack1LanguageCode: 'en', 13 | captionsTextTrack2Label: 'Spanish', 14 | captionsTextTrack2LanguageCode: 'es' 15 | }, 16 | playback: { 17 | audioLanguage: '', 18 | textLanguage: '', 19 | volume: 1, 20 | playsinline: true, 21 | preload: 'none', 22 | autoplay: false, 23 | loop: false, 24 | autopause: false, 25 | allowMutedAutoPlay: true, 26 | updateAudioDescriptionLabels: true, 27 | muted: false, 28 | pictureInPicture: true, 29 | options: { 30 | html5: { 31 | hls: {}, 32 | dash: {}, 33 | native: {} 34 | } 35 | }, 36 | preferNative: { 37 | hls: false, 38 | dash: false 39 | }, 40 | inBrowserFullscreen: false, 41 | screenLockOrientionMode: ScreenOrientationType.NONE, 42 | playAdsWithMSE: false, 43 | streamPriority: [ 44 | { 45 | engine: 'html5', 46 | format: 'hls' 47 | }, 48 | { 49 | engine: 'html5', 50 | format: 'dash' 51 | }, 52 | { 53 | engine: 'html5', 54 | format: 'progressive' 55 | }, 56 | { 57 | engine: 'flash', 58 | format: 'hls' 59 | } 60 | ] 61 | }, 62 | streaming: { 63 | forceBreakStall: false 64 | }, 65 | abr: { 66 | enabled: true, 67 | fpsDroppedFramesInterval: 5000, 68 | fpsDroppedMonitoringThreshold: 0.2, 69 | capLevelOnFPSDrop: false, 70 | capLevelToPlayerSize: false, 71 | restrictions: { 72 | minHeight: 0, 73 | maxHeight: Infinity, 74 | minWidth: 0, 75 | maxWidth: Infinity, 76 | minBitrate: 0, 77 | maxBitrate: Infinity 78 | } 79 | }, 80 | drm: { 81 | keySystem: '' 82 | }, 83 | network: { 84 | maxStaleLevelReloads: 20 85 | } 86 | }; 87 | 88 | const DefaultSources = { 89 | options: {}, 90 | metadata: {} 91 | }; 92 | 93 | export {DefaultConfig, DefaultSources}; 94 | -------------------------------------------------------------------------------- /src/state/state-type.ts: -------------------------------------------------------------------------------- 1 | export const StateType = { 2 | IDLE: 'idle', 3 | LOADING: 'loading', 4 | PLAYING: 'playing', 5 | PAUSED: 'paused', 6 | BUFFERING: 'buffering' 7 | } as const; 8 | -------------------------------------------------------------------------------- /src/state/state.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This class describes a player state. 3 | * @classdesc 4 | */ 5 | export default class State { 6 | /** 7 | * The type of the state. 8 | * Can be one of those describes in states.js 9 | * @member 10 | * @type {string} 11 | * @public 12 | */ 13 | public type: string; 14 | /** 15 | * The duration that the player was in this state. 16 | * @member 17 | * @type {number} 18 | * @private 19 | */ 20 | private _duration: number; 21 | /** 22 | * The timestamp that this state started. 23 | * @member 24 | * @type {number} 25 | * @private 26 | */ 27 | private _timestamp: number; 28 | 29 | /** 30 | * @constructor 31 | * @param {string} type - The type of the state. 32 | */ 33 | constructor(type: string) { 34 | this.type = type; 35 | this._duration = 0; 36 | this._timestamp = Date.now() / 1000; 37 | } 38 | 39 | /** 40 | * Getter for the duration of the state. 41 | * @returns {number} - The duration of the state 42 | */ 43 | public get duration(): number { 44 | return this._duration; 45 | } 46 | 47 | /** 48 | * Setter for the duration of the state. 49 | * @param {number} endTime - The timestamp of the next state. 50 | */ 51 | public set duration(endTime: number) { 52 | this._duration = endTime - this._timestamp; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/thumbnail/thumbnail-info.ts: -------------------------------------------------------------------------------- 1 | class ThumbnailInfo { 2 | private _url: string; 3 | private _width: number; 4 | private _height: number; 5 | private _x: number; 6 | private _y: number; 7 | 8 | constructor(info: {url: string, width: number, height: number, x: number, y: number}) { 9 | const {url, width, height, x, y} = info; 10 | this._url = url; 11 | this._width = width; 12 | this._height = height; 13 | this._x = x; 14 | this._y = y; 15 | } 16 | 17 | public get url(): string { 18 | return this._url; 19 | } 20 | 21 | public get width(): number { 22 | return this._width; 23 | } 24 | 25 | public get height(): number { 26 | return this._height; 27 | } 28 | 29 | public get x(): number { 30 | return this._x; 31 | } 32 | 33 | public get y(): number { 34 | return this._y; 35 | } 36 | } 37 | 38 | export {ThumbnailInfo}; 39 | -------------------------------------------------------------------------------- /src/track/abr-mode-type.ts: -------------------------------------------------------------------------------- 1 | export const AbrMode = { 2 | MANUAL: 'manual', 3 | AUTO: 'auto' 4 | } as const; 5 | -------------------------------------------------------------------------------- /src/track/audio-track.ts: -------------------------------------------------------------------------------- 1 | import Track from './track'; 2 | 3 | enum FlavorAssetTags { 4 | AUDIO_ONLY = 'audio_only', 5 | AUDIO_DESCRIPTION = 'audio_description' 6 | } 7 | 8 | export enum AudioTrackKind { 9 | MAIN = 'main', 10 | DESCRIPTION = 'description' 11 | } 12 | 13 | /** 14 | * Audio track representation of the player. 15 | * @classdesc 16 | */ 17 | class AudioTrack extends Track { 18 | /** 19 | * The kind of the track. i.e. description, main. 20 | * @member 21 | * @type {AudioTrackKind} 22 | * @private 23 | */ 24 | private _kind: AudioTrackKind; 25 | 26 | /** 27 | * Getter for the kind value of the track. 28 | * @public 29 | * @returns {AudioTrackKind} - The kind value of the track. 30 | */ 31 | public get kind(): AudioTrackKind { 32 | return this._kind; 33 | } 34 | 35 | /** 36 | * Setter for the kind value of the track. 37 | * @public 38 | * @param {AudioTrackKind} value - The kind of the track. 39 | */ 40 | public set kind(value: AudioTrackKind) { 41 | this._kind = value; 42 | } 43 | 44 | /** 45 | * @constructor 46 | * @param {Object} settings - The track settings object. 47 | */ 48 | constructor(settings: any = {}) { 49 | super(settings); 50 | this._kind = settings.kind || AudioTrackKind.MAIN; 51 | } 52 | } 53 | 54 | export function audioDescriptionTrackHandler(tracks: Track[], audioFlavors?: Array): void { 55 | if (tracks?.length) { 56 | const hasAudioFlavors = audioFlavors && audioFlavors.length > 0; 57 | let audioTracksIndex = 0; 58 | // iterate over the audio tracks and set the isAudioDescription flag based on the audioFlavors tags 59 | tracks.forEach((track) => { 60 | if (track instanceof AudioTrack) { 61 | const isAudioDescription = (hasAudioFlavors && audioFlavors[audioTracksIndex]?.tags?.includes(FlavorAssetTags.AUDIO_DESCRIPTION)) || track.kind.includes(AudioTrackKind.DESCRIPTION); 62 | if (isAudioDescription) { 63 | // set the language to ad- for audio description tracks 64 | track.language = `ad-${track.language}`; 65 | if (hasAudioFlavors && !audioFlavors[audioTracksIndex]?.label) { 66 | // add "Audio Description" to the label if custom label is not provided 67 | track.label = `${track.label} - Audio Description`; 68 | } 69 | } 70 | audioTracksIndex++; 71 | } 72 | }); 73 | } 74 | } 75 | 76 | export default AudioTrack; 77 | -------------------------------------------------------------------------------- /src/track/image-track.ts: -------------------------------------------------------------------------------- 1 | //@flow 2 | import Track from './track'; 3 | 4 | class ImageTrack extends Track { 5 | private _url: string; 6 | private _width: number; 7 | private _height: number; 8 | private _duration: number; 9 | private _rows: number; 10 | private _cols: number; 11 | private _customData: any; 12 | 13 | constructor(settings: { 14 | id: string, 15 | active: boolean, 16 | index: number, 17 | url: string, 18 | width: number, 19 | height: number, 20 | duration: number, 21 | rows: number, 22 | cols: number, 23 | customData: any 24 | }) { 25 | super(settings); 26 | const {url, width, height, duration, rows, cols, customData} = settings; 27 | this._url = url; 28 | this._width = width; 29 | this._height = height; 30 | this._duration = duration; 31 | this._customData = customData; 32 | this._rows = rows || 1; 33 | this._cols = cols || 1; 34 | } 35 | 36 | public get url(): string { 37 | return this._url; 38 | } 39 | 40 | public get width(): number { 41 | return this._width; 42 | } 43 | 44 | public get height(): number { 45 | return this._height; 46 | } 47 | 48 | public get duration(): number { 49 | return this._duration; 50 | } 51 | 52 | public get rows(): number { 53 | return this._rows; 54 | } 55 | 56 | public get cols(): number { 57 | return this._cols; 58 | } 59 | 60 | public get customData(): any { 61 | return this._customData; 62 | } 63 | 64 | public get sliceWidth(): number { 65 | return this._width / this._cols; 66 | } 67 | 68 | public get sliceHeight(): number { 69 | return this._height / this._rows; 70 | } 71 | } 72 | 73 | export default ImageTrack; 74 | -------------------------------------------------------------------------------- /src/track/label-options.ts: -------------------------------------------------------------------------------- 1 | const LabelOptions = { 2 | AUDIO: 'audio', 3 | CAPTIONS: 'captions', 4 | QUALITIES: 'qualities' 5 | }; 6 | 7 | export {LabelOptions}; 8 | -------------------------------------------------------------------------------- /src/track/text-track.ts: -------------------------------------------------------------------------------- 1 | import Track from './track'; 2 | import Error from '../error/error'; 3 | 4 | type TrackSettings = { 5 | kind?: string; 6 | external?: boolean; 7 | default?: boolean; 8 | active?: boolean; 9 | label?: string; 10 | language?: string; 11 | }; 12 | 13 | export default class TextTrack extends Track { 14 | public static MODE: Record<'DISABLED' | 'SHOWING' | 'HIDDEN', TextTrackMode> = { 15 | DISABLED: 'disabled', 16 | SHOWING: 'showing', 17 | HIDDEN: 'hidden' 18 | }; 19 | 20 | public static KIND: Record<'METADATA' | 'SUBTITLES' | 'CAPTIONS', TextTrackKind> = { 21 | METADATA: 'metadata', 22 | SUBTITLES: 'subtitles', 23 | CAPTIONS: 'captions' 24 | }; 25 | 26 | public static EXTERNAL_TRACK_ID = 'playkit-external-track'; 27 | private static _tracksCount: number = 0; 28 | 29 | private _kind: string; 30 | private _external: boolean; 31 | private _default: boolean; 32 | private _mode: string | undefined; 33 | 34 | constructor(settings: TrackSettings = {}) { 35 | super(settings); 36 | // use language tag if no display label is available 37 | this._label = this.label || this.language; 38 | this._kind = settings.kind!; 39 | this._external = settings.external!; 40 | this._index = TextTrack._generateIndex(); 41 | this._default = settings.default || false; 42 | } 43 | 44 | public static _generateIndex(): number { 45 | return TextTrack._tracksCount++; 46 | } 47 | 48 | public static reset(): void { 49 | TextTrack._tracksCount = 0; 50 | } 51 | 52 | public get mode(): string | undefined { 53 | return this._mode; 54 | } 55 | public set mode(mode: string) { 56 | this._mode = mode; 57 | } 58 | 59 | public get kind(): string { 60 | return this._kind; 61 | } 62 | 63 | public get external(): boolean { 64 | return this._external; 65 | } 66 | 67 | public get default(): boolean { 68 | return this._default; 69 | } 70 | 71 | public static isMetaDataTrack(track: any): boolean { 72 | return track && track.kind === TextTrack.KIND.METADATA; 73 | } 74 | 75 | public static isNativeTextTrack(track: any): boolean { 76 | return track && [TextTrack.KIND.SUBTITLES, TextTrack.KIND.CAPTIONS].includes(track.kind); 77 | } 78 | 79 | public static isExternalTrack(track: any): boolean { 80 | return track && [track.language, track.label].includes(TextTrack.EXTERNAL_TRACK_ID); 81 | } 82 | } 83 | 84 | /** 85 | * Normalize cues to be of type of VTT model. 86 | * @param {TextTrackCueList} textTrackCueList - The text track cue list contains the cues. 87 | * @returns {void} 88 | * @private 89 | */ 90 | function getActiveCues(textTrackCueList: TextTrackCueList): Array { 91 | const normalizedCues: Array = []; 92 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 93 | // @ts-ignore 94 | for (const cue of textTrackCueList) { 95 | //Normalize cues to be of type of VTT model 96 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 97 | // @ts-ignore 98 | if ((VTTCue && cue instanceof VTTCue) || (window.DataCue && cue instanceof window.DataCue)) { 99 | normalizedCues.push(cue); 100 | } else if (TextTrackCue && cue instanceof TextTrackCue) { 101 | try { 102 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 103 | // @ts-ignore 104 | normalizedCues.push(new VTTCue(cue.startTime, cue.endTime, cue.text)); 105 | } catch (error) { 106 | new Error(Error.Severity.RECOVERABLE, Error.Category.TEXT, Error.Code.UNABLE_TO_CREATE_TEXT_CUE, error); 107 | } 108 | } 109 | } 110 | return normalizedCues; 111 | } 112 | 113 | export { getActiveCues, TextTrack as PKTextTrack }; 114 | -------------------------------------------------------------------------------- /src/track/timed-metadata.ts: -------------------------------------------------------------------------------- 1 | import {PKTextTrackCue} from '../types'; 2 | 3 | class TimedMetadata { 4 | public static TYPE: {[type: string]: string}; 5 | 6 | public startTime: number; 7 | public endTime: number; 8 | public id: string; 9 | public type: string; 10 | public metadata: string | any; 11 | /** 12 | * @constructor 13 | * @param {number} startTime - start time. 14 | * @param {number} endTime - end time. 15 | * @param {string} id - id. 16 | * @param {string} type - type. 17 | * @param {any} metadata - metadata. 18 | */ 19 | constructor(startTime: number, endTime: number, id: string, type: string, metadata: any) { 20 | this.startTime = startTime; 21 | this.endTime = endTime; 22 | this.id = id; 23 | this.type = type; 24 | this.metadata = metadata; 25 | } 26 | } 27 | 28 | TimedMetadata.TYPE = { 29 | ID3: 'id3', 30 | EMSG: 'emsg', 31 | CUE_POINT: 'cuepoint', 32 | CUSTOM: 'custom' 33 | }; 34 | 35 | /** 36 | * Create a standard TextTrackCue. 37 | * @param {TimedMetadata} timedMetadata - timed metadata object. 38 | * @returns {TextTrackCue} - the created text track cue 39 | * @private 40 | */ 41 | function createTextTrackCue(timedMetadata: TimedMetadata): PKTextTrackCue | null { 42 | try { 43 | const {startTime, endTime, id, type, metadata} = timedMetadata; 44 | const cue = new VTTCue(startTime, endTime, ''); 45 | const cueValue = {key: type, data: metadata}; 46 | cue.id = id; 47 | cue['value'] = cueValue; 48 | return cue as unknown as PKTextTrackCue; 49 | } catch (e) { 50 | return null; 51 | } 52 | } 53 | 54 | /** 55 | * Create a timed metadata object from a standard TextTrackCue. 56 | * @param {TextTrackCue} cue - text track cue. 57 | * @returns {?TimedMetadata} - the created timed metadata object. 58 | * @private 59 | */ 60 | function createTimedMetadata(cue: TextTrackCue): TimedMetadata | null { 61 | if (cue) { 62 | const {startTime, endTime, id} = cue; 63 | const typeAndMetadata = _getTypeAndMetadata(cue); 64 | if (typeAndMetadata) { 65 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 66 | // @ts-ignore 67 | const {type, metadata} = _getTypeAndMetadata(cue); 68 | return new TimedMetadata(startTime, endTime, id, type, metadata); 69 | } 70 | } 71 | return null; 72 | } 73 | 74 | /** 75 | * @param {TextTrackCue} cue - cue 76 | * @return {Object} - type and data 77 | * @private 78 | */ 79 | function _getTypeAndMetadata(cue: TextTrackCue | PKTextTrackCue): { metadata: any; type: string } | null { 80 | if (cue) { 81 | if ('value' in cue && cue.value) { 82 | const {type, value, track} = cue; 83 | const {key, data} = value; 84 | const isId3 = type === 'org.id3' || (track && track.label) === 'id3'; 85 | let timedMetadataType = Object.values(TimedMetadata.TYPE).find(type => type === key); 86 | if (!timedMetadataType) { 87 | timedMetadataType = isId3 ? TimedMetadata.TYPE.ID3 : TimedMetadata.TYPE.CUSTOM; 88 | } 89 | return { 90 | type: timedMetadataType, 91 | metadata: isId3 || !data ? value : data 92 | }; 93 | } 94 | } 95 | return null; 96 | } 97 | 98 | export {TimedMetadata, createTextTrackCue, createTimedMetadata}; 99 | -------------------------------------------------------------------------------- /src/track/track-type.ts: -------------------------------------------------------------------------------- 1 | export const TrackType = { 2 | VIDEO: 'video', 3 | AUDIO: 'audio', 4 | TEXT: 'text', 5 | IMAGE: 'image' 6 | } as const; 7 | 8 | export type TrackTypes = 'video' | 'audio' | 'text' | 'image' 9 | -------------------------------------------------------------------------------- /src/track/video-track.ts: -------------------------------------------------------------------------------- 1 | //@flow 2 | import Track from './track'; 3 | 4 | /** 5 | * Video track representation of the player. 6 | * @classdesc 7 | */ 8 | export default class VideoTrack extends Track { 9 | /** 10 | * @member {number} _bandwidth - The bandwidth of the video track 11 | * @type {number} 12 | * @private 13 | */ 14 | private _bandwidth: number; 15 | 16 | /** 17 | * @member {number} _width - The width of the video track 18 | * @type {number} 19 | * @private 20 | */ 21 | private _width: number; 22 | 23 | /** 24 | * @member {number} _height - The height of the video track 25 | * @type {number} 26 | * @private 27 | */ 28 | private _height: number; 29 | 30 | /** 31 | * @public 32 | * @returns {number} - The bandwidth of the video track 33 | */ 34 | public get bandwidth(): number { 35 | return this._bandwidth; 36 | } 37 | 38 | /** 39 | * @public 40 | * @returns {number} - The width of the video track 41 | */ 42 | public get width(): number { 43 | return this._width; 44 | } 45 | 46 | /** 47 | * @public 48 | * @returns {number} - The height of the video track 49 | */ 50 | public get height(): number { 51 | return this._height; 52 | } 53 | 54 | /** 55 | * @constructor 56 | * @param {Object} settings - The track settings object 57 | */ 58 | constructor(settings: any = {}) { 59 | super(settings); 60 | this._bandwidth = settings.bandwidth; 61 | this._width = settings.width; 62 | this._height = settings.height; 63 | this._label = settings.label ? settings.label : this._height ? this._height + 'p' : undefined; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/track/vtt-region.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 vtt.js Contributors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | const scrollSetting = { 17 | '': true, 18 | up: true 19 | }; 20 | 21 | /** 22 | * find scroll setting 23 | * @param {string} value - a string 24 | * @returns {*} the settings 25 | */ 26 | function findScrollSetting(value): string | boolean { 27 | if (typeof value !== 'string') { 28 | return false; 29 | } 30 | const scroll = scrollSetting[value.toLowerCase()]; 31 | return scroll ? value.toLowerCase() : false; 32 | } 33 | 34 | /** 35 | * check percentage validation 36 | * @param {number} value - percentage 37 | * @returns {boolean} - boolean 38 | */ 39 | function isValidPercentValue(value): boolean { 40 | return typeof value === 'number' && value >= 0 && value <= 100; 41 | } 42 | 43 | // VTTRegion shim http://dev.w3.org/html5/webvtt/#vttregion-interface 44 | class VTTRegion { 45 | private _width: number = 100; 46 | private _lines: number = 3; 47 | private _regionAnchorX: number = 0; 48 | private _regionAnchorY: number = 100; 49 | private _viewportAnchorX: number = 0; 50 | private _viewportAnchorY: number = 100; 51 | private _scroll: string = ''; 52 | 53 | constructor() {} 54 | 55 | public get width(): number { 56 | return this._width; 57 | } 58 | 59 | public set width(value: number) { 60 | if (!isValidPercentValue(value)) { 61 | throw new Error('Width must be between 0 and 100.'); 62 | } 63 | this._width = value; 64 | } 65 | 66 | public get scroll(): string { 67 | return this._scroll; 68 | } 69 | 70 | public set scroll(value: number) { 71 | const setting = findScrollSetting(value); 72 | // Have to check for false as an empty string is a legal value. 73 | if (setting === false) { 74 | throw new SyntaxError('An invalid or illegal string was specified.'); 75 | } 76 | this._scroll = setting as string; 77 | } 78 | 79 | public get viewportAnchorY(): number { 80 | return this._viewportAnchorY; 81 | } 82 | 83 | public set viewportAnchorY(value: number) { 84 | if (!isValidPercentValue(value)) { 85 | throw new Error('ViewportAnchorY must be between 0 and 100.'); 86 | } 87 | this._viewportAnchorY = value; 88 | } 89 | 90 | public get viewportAnchorX(): number { 91 | return this._viewportAnchorX; 92 | } 93 | 94 | public set viewportAnchorX(value: number) { 95 | if (!isValidPercentValue(value)) { 96 | throw new Error('ViewportAnchorX must be between 0 and 100.'); 97 | } 98 | this._viewportAnchorX = value; 99 | } 100 | 101 | public get regionAnchorX(): number { 102 | return this._regionAnchorX; 103 | } 104 | 105 | public set regionAnchorX(value: number) { 106 | if (!isValidPercentValue(value)) { 107 | throw new Error('RegionAnchorY must be between 0 and 100.'); 108 | } 109 | this._regionAnchorX = value; 110 | } 111 | 112 | public get lines(): number { 113 | return this._lines; 114 | } 115 | 116 | public set lines(value: number) { 117 | if (typeof value !== 'number') { 118 | throw new TypeError('Lines must be set to a number.'); 119 | } 120 | this._lines = value; 121 | } 122 | 123 | public get regionAnchorY(): number { 124 | return this._regionAnchorY; 125 | } 126 | 127 | public set regionAnchorY(value: number) { 128 | if (!isValidPercentValue(value)) { 129 | throw new Error('RegionAnchorX must be between 0 and 100.'); 130 | } 131 | this._regionAnchorY = value; 132 | } 133 | } 134 | 135 | let Region; 136 | if (typeof window !== 'undefined' && window.VTTRegion) { 137 | Region = window.VTTRegion; 138 | } else { 139 | Region = VTTRegion; 140 | } 141 | 142 | export {Region}; 143 | -------------------------------------------------------------------------------- /src/types/abr-config.ts: -------------------------------------------------------------------------------- 1 | export type PKAbrConfigObject = { 2 | fpsDroppedMonitoringThreshold: number, 3 | fpsDroppedFramesInterval: number, 4 | capLevelOnFPSDrop: boolean 5 | }; 6 | -------------------------------------------------------------------------------- /src/types/ad-break-options.ts: -------------------------------------------------------------------------------- 1 | export type PKAdBreakOptions = { 2 | type?: string, 3 | position?: number, 4 | numAds?: number 5 | }; 6 | -------------------------------------------------------------------------------- /src/types/ad-options.ts: -------------------------------------------------------------------------------- 1 | export type PKAdOptions = { 2 | system?: string, 3 | url?: string, 4 | contentType?: string, 5 | title?: string, 6 | position?: number, 7 | duration?: number, 8 | clickThroughUrl?: string, 9 | posterUrl?: string, 10 | skipOffset?: number, 11 | linear: boolean, 12 | width: number, 13 | height: number, 14 | bitrate: number, 15 | bumper: boolean, 16 | inStream?: boolean, 17 | vpaid?: boolean, 18 | streamId?: string, 19 | wrapperAdIds: Array, 20 | wrapperCreativeIds: Array, 21 | wrapperAdSystems: Array 22 | }; 23 | -------------------------------------------------------------------------------- /src/types/auto-play-types.ts: -------------------------------------------------------------------------------- 1 | export type PKAutoPlayTypes = {[type: string]: string | boolean}; 2 | -------------------------------------------------------------------------------- /src/types/custom-labels-config.ts: -------------------------------------------------------------------------------- 1 | export type PKCustomLabelsConfigObject = { 2 | audio: Function, 3 | qualities: Function, 4 | video: Function 5 | }; 6 | -------------------------------------------------------------------------------- /src/types/deferred-promise.ts: -------------------------------------------------------------------------------- 1 | export type DeferredPromise = { 2 | resolve: (value?: any | PromiseLike) => void; 3 | reject: (reason?: any) => void; 4 | catch: (param: () => any) => void; 5 | then(param: () => void): any; 6 | }; 7 | -------------------------------------------------------------------------------- /src/types/dimensions.ts: -------------------------------------------------------------------------------- 1 | export type PKPlayerDimensions = { 2 | width: number, 3 | height: number 4 | }; 5 | 6 | export type PKDimensionsConfig = { 7 | width: number, 8 | height: number, 9 | ratio?: string 10 | }; 11 | -------------------------------------------------------------------------------- /src/types/drm-config.ts: -------------------------------------------------------------------------------- 1 | export type PKDrmConfigObject = { 2 | keySystem: string 3 | }; 4 | -------------------------------------------------------------------------------- /src/types/drm-data.ts: -------------------------------------------------------------------------------- 1 | export type PKDrmDataObject = { 2 | licenseUrl: string, 3 | scheme: string, 4 | certificate?: string 5 | }; 6 | -------------------------------------------------------------------------------- /src/types/engine-decorator-provider.ts: -------------------------------------------------------------------------------- 1 | import {IEngine} from './interfaces/engine'; 2 | import {IEngineDecorator} from './interfaces/engine-decorator'; 3 | 4 | 5 | export interface IEngineDecoratorProvider { 6 | getEngineDecorator(engine: IEngine, dispatchEventHandler: Function): IEngineDecorator; 7 | getName(): string; 8 | } 9 | -------------------------------------------------------------------------------- /src/types/event-types.ts: -------------------------------------------------------------------------------- 1 | import { AdEventType, CustomEventType, Html5EventType } from "../playkit"; 2 | 3 | export type PKEventTypes = typeof Html5EventType & typeof CustomEventType & typeof AdEventType; 4 | -------------------------------------------------------------------------------- /src/types/exteranl-thumbnails-object.ts: -------------------------------------------------------------------------------- 1 | export type PKExternalThumbnailsConfig = { 2 | vttUrl: string 3 | }; 4 | -------------------------------------------------------------------------------- /src/types/external-caption-object.ts: -------------------------------------------------------------------------------- 1 | export type PKExternalCaptionObject = { 2 | url: string, 3 | label: string, 4 | language: string, 5 | default?: boolean, 6 | type?: string 7 | }; 8 | -------------------------------------------------------------------------------- /src/types/global/globals.d.ts: -------------------------------------------------------------------------------- 1 | // globals.d.ts 2 | declare class WebKitMediaKeys { 3 | constructor(keySystem: string) 4 | public readonly keySystem: string; 5 | public static createSession(type: string, optional, initData: Uint8Array); 6 | public static isTypeSupported(keySystem: string, type: string); 7 | } 8 | -------------------------------------------------------------------------------- /src/types/image-player-options.ts: -------------------------------------------------------------------------------- 1 | export type ImageSourceOptions = { 2 | thumbnailAPIParams: { [parmaName: string]: string } 3 | }; 4 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './abr-config' 2 | export * from './ad-break-options' 3 | export * from './ad-options' 4 | export * from './custom-labels-config' 5 | export * from './deferred-promise' 6 | export * from './dimensions' 7 | export * from './drm-config' 8 | export * from './drm-data' 9 | export * from './engine-decorator-provider' 10 | export * from './event-types' 11 | export * from './exteranl-thumbnails-object' 12 | export * from './image-player-options' 13 | export * from './log-level' 14 | export * from './media-source' 15 | export * from './media-source-capabilities' 16 | export * from './media-source-options' 17 | export * from './metadata-config' 18 | export * from './mime-types' 19 | export * from './network-config' 20 | export * from './play-options' 21 | export * from './playback-config' 22 | export * from './playback-options' 23 | export * from './player-state' 24 | export * from './prefer-native-config' 25 | export * from './request' 26 | export * from './request-types' 27 | export * from './response' 28 | export * from './restrictions-types' 29 | export * from './screen-orientation-type' 30 | export * from './session-config' 31 | export * from './sources-config' 32 | export * from './stream-types' 33 | export * from './stats' 34 | export * from './stream-priority' 35 | export * from './stream-types' 36 | export * from './streaming-config' 37 | export * from './text-config' 38 | export * from './text-style' 39 | export * from './text-track-cue' 40 | export * from './text-track-display-setting' 41 | export * from './thumbnail-vtt-cue' 42 | export * from './track-types' 43 | export * from './video-dimensions' 44 | export * from './video-element-store' 45 | export * from './interfaces' 46 | export * from '../track/track-type' 47 | export * from './logger-levels'; 48 | export * from 'js-logger'; -------------------------------------------------------------------------------- /src/types/interfaces/drm-protocol.ts: -------------------------------------------------------------------------------- 1 | import {PKDrmConfigObject} from '../drm-config'; 2 | import {PKDrmDataObject} from '../drm-data'; 3 | 4 | export interface IDrmProtocol { 5 | isConfigured(drmData: Array, drmConfig: PKDrmConfigObject): boolean; 6 | canPlayDrm(drmData: Array): boolean; 7 | setDrmPlayback(...any): void; 8 | } 9 | -------------------------------------------------------------------------------- /src/types/interfaces/engine-capabilty.ts: -------------------------------------------------------------------------------- 1 | export type CapabilityResult = {[capabilityName: string]: any}; 2 | 3 | export interface ICapability { 4 | runCapability(): void; 5 | getCapability(): Promise; 6 | setCapabilities(capabilities: {[name: string]: any}): void; 7 | } 8 | -------------------------------------------------------------------------------- /src/types/interfaces/engine-decorator.ts: -------------------------------------------------------------------------------- 1 | import VideoTrack from '../../track/video-track'; 2 | import AudioTrack from '../../track/audio-track'; 3 | import TextTrack from '../../track/text-track'; 4 | import { FakeEvent } from '../../event/fake-event'; 5 | import {PKMediaSourceObject} from '../media-source'; 6 | 7 | export interface IEngineDecorator { 8 | active: boolean; 9 | dispatchEvent(event: FakeEvent): boolean; 10 | restore?: (source: PKMediaSourceObject, config: any) => void; 11 | reset?: () => void; 12 | destroy?: () => void; 13 | attach?: () => void; 14 | detach?: () => void; 15 | getVideoElement?: () => HTMLVideoElement; 16 | selectVideoTrack?: (videoTrack: VideoTrack) => void; 17 | selectAudioTrack?: (audioTrack: AudioTrack) => void; 18 | selectTextTrack?: (textTrack: TextTrack) => void; 19 | hideTextTrack?: () => void; 20 | enableAdaptiveBitrate?: () => void; 21 | isAdaptiveBitrateEnabled?: () => boolean; 22 | seekToLiveEdge?: () => void; 23 | getStartTimeOfDvrWindow?: () => number; 24 | isLive?: () => boolean; 25 | play?: () => void; 26 | pause?: () => void; 27 | load?: (startTime?: number) => Promise; 28 | enterPictureInPicture?: () => void; 29 | exitPictureInPicture?: () => void; 30 | isPictureInPictureSupported?: () => boolean; 31 | resetAllCues?: () => void; 32 | attachMediaSource?: () => void; 33 | detachMediaSource?: () => void; 34 | id?: string; 35 | src?: string; 36 | currentTime?: number; 37 | duration?: number; 38 | volume?: number; 39 | paused?: boolean; 40 | seeking?: boolean; 41 | seekable?: TimeRanges; 42 | played?: TimeRanges; 43 | buffered?: TimeRanges; 44 | muted?: boolean; 45 | defaultMuted?: boolean; 46 | poster?: string; 47 | preload?: string; 48 | autoplay?: boolean; 49 | loop?: boolean; 50 | controls?: boolean; 51 | playbackRates?: Array; 52 | playbackRate?: number; 53 | defaultPlaybackRate?: number; 54 | ended?: boolean; 55 | error?: MediaError; 56 | networkState?: number; 57 | readyState?: number; 58 | videoHeight?: number; 59 | videoWidth?: number; 60 | playsinline?: boolean; 61 | crossOrigin?: string; 62 | isInPictureInPicture?: boolean; 63 | targetBuffer?: number; 64 | availableBuffer?: number; 65 | } 66 | -------------------------------------------------------------------------------- /src/types/interfaces/engine.ts: -------------------------------------------------------------------------------- 1 | import VideoTrack from '../../track/video-track'; 2 | import AudioTrack from '../../track/audio-track'; 3 | import { FakeEventTarget } from '../../event/fake-event-target'; 4 | import { ThumbnailInfo } from '../../thumbnail/thumbnail-info'; 5 | import ImageTrack from '../../track/image-track'; 6 | import { PKMediaSourceObject } from '../media-source'; 7 | import { PKDrmConfigObject } from '../drm-config'; 8 | import { PKDrmDataObject } from '../drm-data'; 9 | import { PKABRRestrictionObject } from '../restrictions-types'; 10 | import Track from '../../track/track'; 11 | import { PKTextTrack } from '../../track/text-track'; 12 | import { IMediaSourceAdapter } from '../../types'; 13 | 14 | export interface IEngineStatic { 15 | id: string; 16 | createEngine(source: PKMediaSourceObject, config: Object, playerId: string): IEngine; 17 | canPlaySource(source: PKMediaSourceObject, preferNative: boolean, drmConfig: PKDrmConfigObject): boolean; 18 | runCapabilities(): void; 19 | getCapabilities(): Promise; 20 | setCapabilities(capabilities: { [name: string]: any }): void; 21 | prepareVideoElement(playerId: string): void; 22 | isSupported(): boolean; 23 | } 24 | 25 | export interface IEngine extends FakeEventTarget { 26 | restore(source: PKMediaSourceObject, config: Object): void; 27 | destroy(): void; 28 | attach(): void; 29 | detach(): void; 30 | play(): Promise | undefined; 31 | pause(): void; 32 | load(startTime?: number): Promise<{ tracks: Track[] }>; 33 | reset(): void; 34 | selectVideoTrack(videoTrack: VideoTrack): void; 35 | selectAudioTrack(audioTrack: AudioTrack): void; 36 | selectTextTrack(textTrack: PKTextTrack): void; 37 | selectImageTrack(imageTrack: ImageTrack): void; 38 | isPictureInPictureSupported(): boolean; 39 | enterPictureInPicture(): void; 40 | exitPictureInPicture(): void; 41 | hideTextTrack(): void; 42 | enableAdaptiveBitrate(): void; 43 | isAdaptiveBitrateEnabled(): boolean; 44 | applyABRRestriction(restrictions: PKABRRestrictionObject): void; 45 | seekToLiveEdge(): void; 46 | getStartTimeOfDvrWindow(): number; 47 | isLive(): boolean; 48 | getVideoElement(): HTMLVideoElement; 49 | resetAllCues(): void; 50 | attachMediaSource(): void; 51 | detachMediaSource(): void; 52 | getThumbnail(time: number): ThumbnailInfo | null; 53 | isOnLiveEdge(): boolean; 54 | addTextTrack(kind: TextTrackKind, label?: string, language?: string): TextTrack | undefined; 55 | getNativeTextTracks(): TextTrack[]; 56 | getDrmInfo(): PKDrmDataObject | null; 57 | setCachedUrls(cachedUrls: string[]); 58 | id: string; 59 | currentTime: number; 60 | duration: number; 61 | liveDuration: number; 62 | volume: number; 63 | paused: boolean; 64 | seeking: boolean; 65 | played: TimeRanges; 66 | buffered: TimeRanges; 67 | videoHeight: number; 68 | videoWidth: number; 69 | muted: boolean; 70 | defaultMuted: boolean; 71 | src: string; 72 | poster: string; 73 | preload: 'none' | 'metadata' | 'auto' | ''; 74 | autoplay: boolean; 75 | controls: boolean; 76 | loop: boolean; 77 | error: MediaError | null; 78 | seekable: TimeRanges; 79 | ended: boolean; 80 | playbackRate: number; 81 | playbackRates: Array; 82 | defaultPlaybackRate: number; 83 | isInPictureInPicture: boolean; 84 | networkState: number; 85 | readyState: number; 86 | playsinline: boolean; 87 | crossOrigin: string | null; 88 | targetBuffer: number; 89 | availableBuffer: number; 90 | mediaSourceAdapter: IMediaSourceAdapter | null; 91 | } 92 | -------------------------------------------------------------------------------- /src/types/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | // export * from './dom'; 2 | export * from './drm-protocol'; 3 | export * from './engine'; 4 | export * from './engine-capabilty'; 5 | export * from './engine-decorator'; 6 | export * from './media-source-adapter'; -------------------------------------------------------------------------------- /src/types/interfaces/media-source-adapter.ts: -------------------------------------------------------------------------------- 1 | import VideoTrack from '../../track/video-track'; 2 | import AudioTrack from '../../track/audio-track'; 3 | import { PKTextTrack } from '../../track/text-track'; 4 | import { PKDrmConfigObject } from '../drm-config'; 5 | import { PKMediaSourceCapabilities } from '../media-source-capabilities'; 6 | import { PKMediaSourceObject } from '../media-source'; 7 | import ImageTrack from '../../track/image-track'; 8 | import { PKDrmDataObject } from '../drm-data'; 9 | import { FakeEventTarget } from '../../event/fake-event-target'; 10 | import { ThumbnailInfo } from '../../thumbnail/thumbnail-info'; 11 | import Track from '../../track/track'; 12 | import TextStyle from '../../track/text-style'; 13 | import { PKABRRestrictionObject } from '../restrictions-types'; 14 | 15 | export interface IMediaSourceAdapterStatic { 16 | id: string; 17 | isSupported(): boolean; 18 | isMSESupported(): boolean; 19 | canPlayType(mimeType: string): boolean; 20 | canPlayDrm(drmData: Array, drmConfig: PKDrmConfigObject): boolean; 21 | createAdapter(videoElement: HTMLVideoElement, source: PKMediaSourceObject, config: Object): IMediaSourceAdapter; 22 | } 23 | 24 | export interface IMediaSourceAdapter extends FakeEventTarget { 25 | src: string; 26 | liveDuration: number; 27 | capabilities: PKMediaSourceCapabilities; 28 | targetBuffer: number; 29 | load(startTime?: number): Promise<{ tracks: Track[] }>; 30 | handleMediaError(error?: MediaError): boolean; 31 | destroy(): Promise; 32 | selectVideoTrack(videoTrack: VideoTrack): void; 33 | selectAudioTrack(audioTrack: AudioTrack): void; 34 | selectTextTrack(textTrack: PKTextTrack): void; 35 | selectImageTrack(imageTrack: ImageTrack): void; 36 | hideTextTrack(): void; 37 | enableAdaptiveBitrate(): void; 38 | isAdaptiveBitrateEnabled(): boolean; 39 | seekToLiveEdge(): void; 40 | isLive(): boolean; 41 | isOnLiveEdge(): boolean; 42 | getStartTimeOfDvrWindow(): number; 43 | setMaxBitrate(bitrate: number): void; 44 | attachMediaSource(): void; 45 | detachMediaSource(): void; 46 | getSegmentDuration(): number; 47 | disableNativeTextTracks(): void; 48 | getThumbnail(time: number): ThumbnailInfo | null; 49 | getDrmInfo(): PKDrmDataObject | null; 50 | applyABRRestriction(restriction: PKABRRestrictionObject): void; 51 | applyTextTrackStyles(sheet: CSSStyleSheet, styles: TextStyle, containerId: string, engineClassName?: string): void; 52 | setCachedUrls(cachedUrls: string[]); 53 | } 54 | -------------------------------------------------------------------------------- /src/types/log-level.ts: -------------------------------------------------------------------------------- 1 | import {ILogHandler} from 'js-logger'; 2 | 3 | export type PKLogConfigObject = { 4 | level: string, 5 | handler?: ILogHandler 6 | }; 7 | -------------------------------------------------------------------------------- /src/types/logger-levels.ts: -------------------------------------------------------------------------------- 1 | import { ILogLevel } from "js-logger"; 2 | 3 | export type LoggerLevels = { TRACE: ILogLevel; TIME: ILogLevel; ERROR: ILogLevel; INFO: ILogLevel; DEBUG: ILogLevel; WARN: ILogLevel; OFF: ILogLevel } 4 | -------------------------------------------------------------------------------- /src/types/media-source-capabilities.ts: -------------------------------------------------------------------------------- 1 | export type PKMediaSourceCapabilities = { 2 | [fpsControl: string]: boolean 3 | }; 4 | -------------------------------------------------------------------------------- /src/types/media-source-options.ts: -------------------------------------------------------------------------------- 1 | export type PKMediaSourceOptionsObject = { 2 | forceRedirectExternalStreams: boolean, 3 | redirectExternalStreamsHandler?: Function, 4 | redirectExternalStreamsTimeout?: number 5 | }; 6 | -------------------------------------------------------------------------------- /src/types/media-source.ts: -------------------------------------------------------------------------------- 1 | import {PKDrmDataObject} from './drm-data'; 2 | 3 | export type PKMediaSourceObject = { 4 | mimetype: string, 5 | url: string, 6 | id?: string, 7 | bandwidth: number, 8 | width: number, 9 | height: number, 10 | drmData?: Array 11 | }; 12 | -------------------------------------------------------------------------------- /src/types/media-types.ts: -------------------------------------------------------------------------------- 1 | export type PKMediaTypes = {[type: string]: string}; 2 | -------------------------------------------------------------------------------- /src/types/metadata-config.ts: -------------------------------------------------------------------------------- 1 | export type PKMetadataConfigObject = { 2 | name?: string, 3 | description?: string, 4 | mediaType?: string, 5 | metas?: Object, 6 | tags?: Object, 7 | epgId?: string, 8 | recordingId?: string 9 | audioFlavors?: Array, 10 | }; 11 | -------------------------------------------------------------------------------- /src/types/mime-types.ts: -------------------------------------------------------------------------------- 1 | export type PKMimeTypes = {[mime: string]: Array}; 2 | -------------------------------------------------------------------------------- /src/types/network-config.ts: -------------------------------------------------------------------------------- 1 | export type PKNetworkConfigObject = { 2 | requestFilter?: Function, 3 | responseFilter?: Function, 4 | maxStaleLevelReloads: number 5 | }; 6 | -------------------------------------------------------------------------------- /src/types/play-options.ts: -------------------------------------------------------------------------------- 1 | export type PKPlayOptionsObject = { 2 | programmatic: boolean 3 | }; 4 | -------------------------------------------------------------------------------- /src/types/playback-config.ts: -------------------------------------------------------------------------------- 1 | import {PKAutoPlayTypes} from './auto-play-types'; 2 | import {PKStreamPriorityObject} from './stream-priority'; 3 | import {PKPreferNativeConfigObject} from './prefer-native-config'; 4 | 5 | export type PKPlaybackConfigObject = { 6 | audioLanguage: string, 7 | textLanguage: string, 8 | captionsDisplay: boolean, 9 | additionalAudioLanguage: string | [string], 10 | additionalTextLanguage: string | [string], 11 | volume: number, 12 | playsinline: boolean, 13 | crossOrigin: string, 14 | preload: string, 15 | autoplay: PKAutoPlayTypes, 16 | allowMutedAutoPlay: boolean, 17 | updateAudioDescriptionLabels: boolean, 18 | muted: boolean, 19 | pictureInPicture: boolean, 20 | streamPriority: Array, 21 | preferNative: PKPreferNativeConfigObject, 22 | inBrowserFullscreen: boolean, 23 | playAdsWithMSE: boolean, 24 | screenLockOrientionMode: string 25 | }; 26 | -------------------------------------------------------------------------------- /src/types/playback-options.ts: -------------------------------------------------------------------------------- 1 | export type PKPlaybackOptionsObject = { 2 | html5: { 3 | hls: any, 4 | dash: any 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /src/types/player-options.ts: -------------------------------------------------------------------------------- 1 | import {PKLogConfigObject} from './log-level'; 2 | import {PKPlaybackConfigObject} from './playback-config'; 3 | import {PKStreamingConfigObject} from './streaming-config'; 4 | import {PKSessionConfigObject} from './session-config'; 5 | import {PKNetworkConfigObject} from './network-config'; 6 | import {PKCustomLabelsConfigObject} from './custom-labels-config'; 7 | 8 | export type PKPlayerOptionsObject = { 9 | log?: PKLogConfigObject, 10 | playback?: PKPlaybackConfigObject, 11 | streaming?: PKStreamingConfigObject, 12 | session?: PKSessionConfigObject, 13 | network?: PKNetworkConfigObject, 14 | customLabels?: PKCustomLabelsConfigObject 15 | }; 16 | -------------------------------------------------------------------------------- /src/types/player-state.ts: -------------------------------------------------------------------------------- 1 | import State from '../state/state'; 2 | 3 | export type Transition = { 4 | [state: string]: { 5 | [event: string]: Function 6 | } 7 | }; 8 | 9 | export type MaybeState = State | null; 10 | 11 | export type StateChanged = { 12 | oldState: MaybeState, 13 | newState: MaybeState 14 | }; 15 | -------------------------------------------------------------------------------- /src/types/prefer-native-config.ts: -------------------------------------------------------------------------------- 1 | export type PKPreferNativeConfigObject = { 2 | hls: boolean, 3 | dash: boolean 4 | }; 5 | -------------------------------------------------------------------------------- /src/types/request-types.ts: -------------------------------------------------------------------------------- 1 | export type PKRequestType = {[request: string]: number}; 2 | -------------------------------------------------------------------------------- /src/types/request.ts: -------------------------------------------------------------------------------- 1 | export type PKRequestObject = { 2 | url: string, 3 | body: ArrayBuffer | ArrayBufferView | string | null; 4 | headers: {[header: string]: string} 5 | }; 6 | -------------------------------------------------------------------------------- /src/types/response.ts: -------------------------------------------------------------------------------- 1 | export type PKResponseObject = { 2 | url: string, 3 | originalUrl: string, 4 | data: ArrayBuffer | ArrayBufferView; 5 | headers: {[header: string]: string} 6 | }; 7 | -------------------------------------------------------------------------------- /src/types/restrictions-types.ts: -------------------------------------------------------------------------------- 1 | export type PKABRRestrictionObject = { 2 | minHeight: number, 3 | maxHeight: number, 4 | minWidth: number, 5 | maxWidth: number, 6 | minBitrate: number, 7 | maxBitrate: number 8 | }; 9 | -------------------------------------------------------------------------------- /src/types/screen-orientation-type.ts: -------------------------------------------------------------------------------- 1 | export type PKOrientationType = {[type: string]: string}; 2 | -------------------------------------------------------------------------------- /src/types/session-config.ts: -------------------------------------------------------------------------------- 1 | export type PKSessionConfigObject = { 2 | id?: string, 3 | ks?: string, 4 | isAnonymous?: boolean, 5 | partnerId?: number, 6 | uiConfId?: number 7 | }; 8 | -------------------------------------------------------------------------------- /src/types/sources-config.ts: -------------------------------------------------------------------------------- 1 | import {PKMediaSourceObject} from './media-source'; 2 | import {PKExternalCaptionObject} from './external-caption-object'; 3 | import {PKExternalThumbnailsConfig} from './exteranl-thumbnails-object'; 4 | import {PKMediaSourceOptionsObject} from './media-source-options'; 5 | import {PKMetadataConfigObject} from './metadata-config'; 6 | import {ImageSourceOptions} from './image-player-options'; 7 | import {PKMediaTypes} from "./media-types"; 8 | 9 | export type PKSourcesConfigObject = { 10 | hls: Array, 11 | dash: Array, 12 | progressive: Array, 13 | image: Array, 14 | document: Array, 15 | captions?: Array, 16 | thumbnails?: PKExternalThumbnailsConfig, 17 | options: PKMediaSourceOptionsObject, 18 | type: string, 19 | dvr: boolean, 20 | metadata: PKMetadataConfigObject, 21 | id?: string, 22 | poster?: string, 23 | duration?: number, 24 | startTime?: number, 25 | endTime?: number, 26 | vr?: any, 27 | imageSourceOptions?: ImageSourceOptions, 28 | seekFrom?: number, 29 | clipTo?: number, 30 | mediaEntryType?: PKMediaTypes 31 | }; 32 | -------------------------------------------------------------------------------- /src/types/stats.ts: -------------------------------------------------------------------------------- 1 | export type PKStatsObject = { 2 | targetBuffer: number, 3 | availableBuffer: number 4 | }; 5 | -------------------------------------------------------------------------------- /src/types/stream-priority.ts: -------------------------------------------------------------------------------- 1 | export type PKStreamPriorityObject = { 2 | engine: string, 3 | format: string 4 | }; 5 | -------------------------------------------------------------------------------- /src/types/stream-types.ts: -------------------------------------------------------------------------------- 1 | export type PlayerStreamTypes = 'dash' | 'hls' | 'progressive' | 'image' | 'document'; 2 | export type PKStreamTypes = Record<'DASH' | 'HLS' | 'PROGRESSIVE' | 'IMAGE' | 'DOCUMENT', PlayerStreamTypes>; 3 | -------------------------------------------------------------------------------- /src/types/streaming-config.ts: -------------------------------------------------------------------------------- 1 | export type PKStreamingConfigObject = { 2 | forceBreakStall: boolean, 3 | lowLatencyMode: boolean, 4 | trackEmsgEvents: boolean, 5 | switchDynamicToStatic: boolean 6 | }; 7 | -------------------------------------------------------------------------------- /src/types/text-config.ts: -------------------------------------------------------------------------------- 1 | import {PKTextStyleObject} from './text-style'; 2 | import {PKTextTrackDisplaySettingObject} from './text-track-display-setting'; 3 | 4 | export interface PKTextConfigObject { 5 | enableCEA708Captions: boolean; 6 | useShakaTextTrackDisplay: boolean; 7 | useNativeTextTrack: boolean; 8 | textTrackDisplaySetting: PKTextTrackDisplaySettingObject; 9 | textStyle: PKTextStyleObject; 10 | forceCenter: boolean; 11 | captionsTextTrack1Label: string; 12 | captionsTextTrack1LanguageCode: string; 13 | captionsTextTrack2Label: string; 14 | captionsTextTrack2LanguageCode: string; 15 | } 16 | -------------------------------------------------------------------------------- /src/types/text-style.ts: -------------------------------------------------------------------------------- 1 | export type FontSizeOptions = '50%' | '75%' | '100%' | '200%' | '300%' | '400%'; 2 | export type FontAlignmentOptions = 'default' | 'left' | 'center' | 'right'; 3 | export type FontScaleOptions = -2 | -1 | 0 | 2 | 3 | 4; 4 | /** 5 | * @typedef {Object} PKTextStyleObject 6 | * @property {"50%" | "75%" | "100%" | "200%" | "300%" | "400%"} fontSize='100%' - Percentage unit relative to the parent element's font size. 7 | * @property {-2 | -1 | 0 | 2 | 3 | 4} fontScale=0 - - Integer number representing the scaling factor relative to the parent element's font size. 8 | * @property {string} fontFamily='sans-serif' 9 | * @property {[number, number, number]} fontColor=[255, 255, 255] - Color in RGB format. 10 | * @property {number} fontOpacity=1 11 | * @property {Array<[number, number, number, number, number, number]>} fontEdge=[] 12 | * @property {[number, number, number]} backgroundColor=[0, 0, 0] - Color in RGB format. 13 | * @property {number} backgroundOpacity=1 14 | */ 15 | export type PKTextStyleObject = { 16 | fontSize: FontSizeOptions; 17 | textAlign: FontAlignmentOptions; 18 | fontScale: FontScaleOptions; 19 | fontFamily: string; 20 | fontColor: [number, number, number]; 21 | fontOpacity: number; 22 | fontEdge: Array<[number, number, number, number, number, number]>; 23 | backgroundColor: [number, number, number]; 24 | backgroundOpacity: number; 25 | }; 26 | -------------------------------------------------------------------------------- /src/types/text-track-cue.ts: -------------------------------------------------------------------------------- 1 | export interface PKTextTrackCue extends TextTrackCue { 2 | value: { key: string, data: string | any }; 3 | type?: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/types/text-track-display-setting.ts: -------------------------------------------------------------------------------- 1 | export type PKTextTrackDisplaySettingObject = { 2 | line: string | number, 3 | lineAlign: string, 4 | align: string, 5 | position: number, 6 | positionAlign: string, 7 | snapToLines: boolean, 8 | vertical: string, 9 | size: number 10 | }; 11 | -------------------------------------------------------------------------------- /src/types/thumbnail-vtt-cue.ts: -------------------------------------------------------------------------------- 1 | export type PKThumbnailVttCue = { 2 | startTime: number, 3 | endTime: number, 4 | imgUrl: string, 5 | coordinates: {x: number, y: number} | null, 6 | size: {height: number, width: number} | null 7 | }; 8 | -------------------------------------------------------------------------------- /src/types/track-types.ts: -------------------------------------------------------------------------------- 1 | export type PKTrackTypes = {[track: string]: string}; 2 | -------------------------------------------------------------------------------- /src/types/video-dimensions.ts: -------------------------------------------------------------------------------- 1 | export type PKVideoDimensionsObject = { 2 | videoHeight: number, 3 | videoWidth: number 4 | }; 5 | -------------------------------------------------------------------------------- /src/types/video-element-store.ts: -------------------------------------------------------------------------------- 1 | export type PKVideoElementStore = {[id: string]: HTMLVideoElement} | {}; 2 | -------------------------------------------------------------------------------- /src/utils/binary-search.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {Array} list The array to search. 3 | * @param {Function} comparisonFn 4 | * Called and provided a candidate item as the first argument. 5 | * Should return: 6 | * > -1 if the item should be located at a lower index than the provided item. 7 | * > 1 if the item should be located at a higher index than the provided item. 8 | * > 0 if the item is the item you're looking for. 9 | * 10 | * @return {any} The object if it is found or null otherwise. 11 | */ 12 | export function binarySearch(list: Array = [], comparisonFn: ((param: T) => number) = (): number => 1): T | null { 13 | if (list.length === 0 || (list.length === 1 && comparisonFn(list[0]) !== 0)) { 14 | return null; 15 | } 16 | const mid = Math.floor(list.length / 2); 17 | if (comparisonFn(list[mid]) === 0) { 18 | return list[mid]; 19 | } 20 | if (comparisonFn(list[mid]) > 0) { 21 | return binarySearch(list.slice(0, mid), comparisonFn); 22 | } else { 23 | return binarySearch(list.slice(mid + 1), comparisonFn); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/utils/env.ts: -------------------------------------------------------------------------------- 1 | import UAParser from 'ua-parser-js'; 2 | import {IEnv} from '../types/ua-parser'; 3 | 4 | const SmartTvRegex = /^.*(smart-tv|smarttv).*$/i; 5 | 6 | const LGTVRegex = /^.*(web0s).*(smarttv).*$/i; 7 | 8 | const SAMSUNGTVRegex = /^.*(smart-tv).*(tizen).*$/i; 9 | 10 | const HISENSETVRegex = /^.*(vidaa).*(smarttv).*$/i; 11 | 12 | //recognize as safari 13 | const SAMSUNGBrowserParser = [ 14 | [SAMSUNGTVRegex], 15 | [ 16 | [UAParser.BROWSER.NAME, 'SAMSUNG_TV_BROWSER'], 17 | [UAParser.BROWSER.MAJOR, ''], 18 | [UAParser.BROWSER.VERSION, ''] 19 | ] 20 | ]; 21 | 22 | //recognize os of smartTV devices 23 | const OSParser = [[LGTVRegex], [UAParser.OS.NAME], [HISENSETVRegex], [UAParser.OS.NAME]]; 24 | 25 | //add smart tv as smart tv devices 26 | const DeviceParser = [ 27 | [LGTVRegex], 28 | [ 29 | [UAParser.DEVICE.VENDOR, 'LG'], 30 | [UAParser.DEVICE.TYPE, UAParser.DEVICE.SMARTTV] 31 | ], 32 | [SAMSUNGTVRegex], 33 | [ 34 | [UAParser.DEVICE.VENDOR, 'SAMSUNG'], 35 | [UAParser.DEVICE.TYPE, UAParser.DEVICE.SMARTTV] 36 | ], 37 | [HISENSETVRegex], 38 | [ 39 | [UAParser.DEVICE.VENDOR, 'HISENSE'], 40 | [UAParser.DEVICE.TYPE, UAParser.DEVICE.SMARTTV] 41 | ], 42 | [SmartTvRegex], 43 | [[UAParser.DEVICE.TYPE, UAParser.DEVICE.SMARTTV]] 44 | ]; 45 | 46 | const EdgeChromiumParser = [[/(edg)\/((\d+)?[\w.]+)/i], [[UAParser.BROWSER.NAME, 'Edge'], UAParser.BROWSER.VERSION, UAParser.BROWSER.MAJOR]]; 47 | 48 | const BrowserParser = [...EdgeChromiumParser, ...SAMSUNGBrowserParser]; 49 | 50 | 51 | const Env: IEnv = new UAParser(undefined, {browser: BrowserParser, device: DeviceParser, os: OSParser}).getResult() as IEnv; 52 | 53 | Env['isConsole'] = Env.device.type === UAParser.DEVICE.CONSOLE; 54 | Env['isSmartTV'] = Env.device.type === UAParser.DEVICE.SMARTTV; 55 | Env['isMobile'] = Env.device.type === UAParser.DEVICE.MOBILE; 56 | Env['isTablet'] = Env.device.type === UAParser.DEVICE.TABLET; 57 | Env['isWearable'] = Env.device.type === UAParser.DEVICE.WEARABLE; 58 | Env['isEmbedded'] = Env.device.type === UAParser.DEVICE.EMBEDDED; 59 | Env['isIPadOS'] = Env.os.name === 'Mac OS' && 'ontouchend' in document; 60 | Env['isSafari'] = Boolean(Env.browser.name?.includes('Safari')); 61 | Env['isIOS'] = Env.os.name === 'iOS'; 62 | Env['isMacOS'] = Env.os.name === 'Mac OS'; 63 | export default Env; 64 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './util'; 2 | export { ResizeWatcher } from './resize-watcher'; 3 | export { MultiMap } from './multi-map'; 4 | export { binarySearch } from './binary-search'; 5 | export * from './styles'; 6 | -------------------------------------------------------------------------------- /src/utils/jsonp.ts: -------------------------------------------------------------------------------- 1 | import Error from '../error/error'; 2 | 3 | const JSONP_TIMEOUT: number = 5000; 4 | const CALLBACK_PREFIX: string = 'jsonpcallback'; 5 | const JSONP_FORMAT_STRING: string = 'responseFormat=jsonp&callback='; 6 | 7 | /** 8 | * JSONP utility. 9 | * @param {string} url - The url of the request. 10 | * @param {string} callback - Callback function to be called when the request returns. 11 | * @param {Object} options - Object contains configuration (currently only timeout). 12 | * @returns {Promise<*>} - A promise with the callback output. 13 | */ 14 | function jsonp(url: string, callback: (...args: any[]) => any, options: { timeout: number}): Promise { 15 | options = options || {} as { timeout: number}; 16 | const timeout = options.timeout ? options.timeout : JSONP_TIMEOUT; 17 | const script = document.createElement('script'); 18 | const callbackId = CALLBACK_PREFIX + Math.round(Date.now() + Math.random() * 1000001); 19 | let scriptUri = url; 20 | let timer; 21 | 22 | /** 23 | * function to clean the DOM from the script tag and from the function 24 | * @returns {void} 25 | */ 26 | const _cleanup = (): void => { 27 | if (script && script.parentNode) { 28 | script.parentNode.removeChild(script); 29 | } 30 | window[callbackId] = (): void => {}; 31 | if (timer) { 32 | clearTimeout(timer); 33 | } 34 | }; 35 | 36 | return new Promise((resolve, reject) => { 37 | if (timeout) { 38 | timer = setTimeout( () => { 39 | _cleanup(); 40 | reject(new Error(Error.Severity.CRITICAL, Error.Category.NETWORK, Error.Code.TIMEOUT, url)); 41 | }, timeout); 42 | } 43 | 44 | /** 45 | * a wrapper to the callback function, to save a closure 46 | * @param {Object} data - the data we get from the server, in response to the request 47 | * @returns {void} 48 | */ 49 | window[callbackId] = (data: any): void => { 50 | const callbackResult = callback(data, url); 51 | _cleanup(); 52 | resolve(callbackResult); 53 | }; 54 | 55 | if (scriptUri.match(/\?/)) { 56 | scriptUri += '&' + JSONP_FORMAT_STRING + callbackId; 57 | } else { 58 | scriptUri += '?' + JSONP_FORMAT_STRING + callbackId; 59 | } 60 | 61 | script.type = 'text/javascript'; 62 | script.src = scriptUri; 63 | document.getElementsByTagName('head')[0].appendChild(script); 64 | }); 65 | } 66 | 67 | export {jsonp}; 68 | -------------------------------------------------------------------------------- /src/utils/locale.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Locale class 3 | * @class 4 | * 5 | */ 6 | export default class Locale { 7 | /** 8 | * tries to return the locale language in IOS-693-1 format(two-letter codes, one per language for) 9 | * @returns {string} - the IOS-693-1 language string 10 | * @static 11 | */ 12 | public static get language(): string { 13 | let lang: string; 14 | 15 | if (navigator.languages && navigator.languages.length) { 16 | // latest versions of Chrome and Firefox set this correctly 17 | lang = navigator.languages[0]; 18 | } else { 19 | // latest versions of Chrome, Firefox, and Safari set this correctly 20 | lang = navigator.language; 21 | } 22 | 23 | if (lang && lang.match('-')) { 24 | lang = lang.split('-')[0]; 25 | } 26 | 27 | return lang; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/utils/logger.ts: -------------------------------------------------------------------------------- 1 | import JsLogger, {ILogger, ILogHandler, ILogLevel} from 'js-logger'; 2 | import { LoggerLevels } from '../types/logger-levels'; 3 | 4 | const LogLevel: LoggerLevels = { 5 | DEBUG: JsLogger.DEBUG, 6 | INFO: JsLogger.INFO, 7 | TIME: JsLogger.TIME, 8 | WARN: JsLogger.WARN, 9 | ERROR: JsLogger.ERROR, 10 | OFF: JsLogger.OFF, 11 | TRACE: JsLogger.TRACE 12 | }; 13 | 14 | const LogLevelType: Record = {} as Record; 15 | 16 | // Build the log level types enums according to the LogLevel object 17 | Object.keys(LogLevel).forEach(key => { 18 | LogLevelType[key] = key; 19 | }); 20 | 21 | JsLogger.useDefaults({defaultLevel: JsLogger.ERROR}); 22 | 23 | /** 24 | * sets the logger handler 25 | * @private 26 | * @param {LogHandlerType} handler - the log level 27 | * @returns {void} 28 | */ 29 | function setLogHandler(handler: ILogHandler): void { 30 | JsLogger.setHandler((messages, context) => handler(messages, context)); 31 | } 32 | 33 | /** 34 | * get a logger 35 | * @param {?string} name - the logger name 36 | * @returns {Object} - the logger class 37 | */ 38 | function getLogger(name?: string): ILogger { 39 | if (!name) { 40 | return JsLogger; 41 | } 42 | return JsLogger.get(name); 43 | } 44 | 45 | /** 46 | * get the log level 47 | * @param {?string} name - the logger name 48 | * @returns {PKLogLevelObject} - the log level 49 | */ 50 | function getLogLevel(name?: string): ILogLevel { 51 | return getLogger(name).getLevel(); 52 | } 53 | 54 | /** 55 | * sets the logger level 56 | * @param {PKLogLevelObject} level - the log level 57 | * @param {?string} name - the logger name 58 | * @returns {void} 59 | */ 60 | function setLogLevel(level: ILogLevel, name?: string): void { 61 | getLogger(name).setLevel(level); 62 | } 63 | 64 | export default getLogger; 65 | export {LogLevel, LogLevelType, getLogLevel, setLogLevel, setLogHandler, getLogger}; 66 | -------------------------------------------------------------------------------- /src/utils/multi-map.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A simple multimap template. 3 | * @constructor 4 | * @struct 5 | * @template T 6 | */ 7 | class MultiMap { 8 | private _map: Map; 9 | 10 | constructor() { 11 | /** @private {!Object.>} */ 12 | this._map = new Map(); 13 | } 14 | 15 | /** 16 | * Add a key, value pair to the map. 17 | * @param {K} key - 18 | * @param {T} value - 19 | * @returns {void} 20 | */ 21 | public push(key: K, value: T): void { 22 | if (this._map.has(key)) { 23 | const list = this._map.get(key); 24 | if (Array.isArray(list)) { 25 | list.push(value); 26 | this._map.set(key, list); 27 | } 28 | } else { 29 | this._map.set(key, [value]); 30 | } 31 | } 32 | 33 | /** 34 | * Set an array of values for the key, overwriting any previous data. 35 | * @param {K} key - 36 | * @param {!Array.} values - 37 | * @returns {void} 38 | */ 39 | public set(key: K, values: T[]): void { 40 | this._map.set(key, values); 41 | } 42 | 43 | /** 44 | * Check for a key. 45 | * @param {K} key - 46 | * @return {boolean} true if the key exists. 47 | */ 48 | public has(key: K): boolean { 49 | return this._map.has(key); 50 | } 51 | 52 | /** 53 | * Get a list of values by key. 54 | * @param {K} key - 55 | * @return {Array.} or null if no such key exists. 56 | */ 57 | public get(key: K): Array { 58 | const list = this._map.get(key); 59 | // slice() clones the list so that it and the map can each be modified 60 | // without affecting the other. 61 | return list ? list.slice() : []; 62 | } 63 | 64 | /** 65 | * Get a list of all values. 66 | * @returns {!Array.} - 67 | */ 68 | public getAll(): T[] { 69 | let list: T[] = []; 70 | for (const value of this._map.values()) { 71 | list = list.concat(value); 72 | } 73 | return list; 74 | } 75 | 76 | /** 77 | * Remove a specific value, if it exists. If there are no more values to the key, the key is removed 78 | * @param {K} key - 79 | * @param {T} value - 80 | * @returns {void} 81 | */ 82 | public remove(key: K, value: T): void { 83 | if (!this._map.has(key)) return; 84 | const list = this._map.get(key); 85 | if (Array.isArray(list)) { 86 | for (let i = 0; i < list.length; ++i) { 87 | if (list[i] === value) { 88 | list.splice(i, 1); 89 | --i; 90 | } 91 | } 92 | if (list.length === 0) { 93 | this._map.delete(key); 94 | } 95 | } 96 | } 97 | 98 | /** 99 | * Get all keys from the multimap. 100 | * @return {!Array.} 101 | */ 102 | // eslint-disable-next-line no-undef 103 | public keys(): Iterator { 104 | return this._map.keys(); 105 | } 106 | 107 | /** 108 | * Clear all keys and values from the multimap. 109 | * @returns {void} 110 | */ 111 | public clear(): void { 112 | this._map.clear(); 113 | } 114 | } 115 | export {MultiMap}; 116 | -------------------------------------------------------------------------------- /src/utils/poster-manager.ts: -------------------------------------------------------------------------------- 1 | import * as Utils from './util'; 2 | 3 | class PosterManager { 4 | /** 5 | * Poster image URL 6 | * @type {string} 7 | * @private 8 | */ 9 | private _posterUrl!: string; 10 | /** 11 | * The poster HTML Div element. 12 | * @type {HTMLDivElement} 13 | * @private 14 | */ 15 | private _el!: HTMLDivElement; 16 | 17 | constructor() { 18 | this._createEl(); 19 | } 20 | 21 | /** 22 | * Set the poster source URL 23 | * @param {string} posterUrl - the poster image URL 24 | * @public 25 | * @returns {void} 26 | */ 27 | public setSrc(posterUrl?: string): void { 28 | if (posterUrl) { 29 | this._posterUrl = posterUrl; 30 | Utils.Dom.setStyle(this._el, 'background-image', `url("${this._posterUrl}")`); 31 | this.hide(); 32 | } 33 | } 34 | 35 | /** 36 | * Get the poster source URL 37 | * @public 38 | * @returns {string} - the poster image URL 39 | */ 40 | public get src(): string { 41 | return this._posterUrl; 42 | } 43 | 44 | /** 45 | * Get the poster HTML Div element 46 | * @public 47 | * @returns {HTMLDivElement} - Poster HTML Dom element 48 | */ 49 | public getElement(): HTMLDivElement { 50 | return this._el; 51 | } 52 | 53 | /** 54 | * Create the HTML Div element of the poster 55 | * @private 56 | * @returns {void} 57 | */ 58 | private _createEl(): void { 59 | if (!this._el) { 60 | const el = (this._el = Utils.Dom.createElement('div')); 61 | Utils.Dom.setAttribute(el, 'id', Utils.Generator.uniqueId(5)); 62 | Utils.Dom.setAttribute(el, 'tabindex', '-1'); 63 | } 64 | } 65 | 66 | /** 67 | * Removes the poster element from the dom 68 | * @private 69 | * @returns {void} 70 | */ 71 | private _removeEl(): void { 72 | if (this._el) { 73 | Utils.Dom.removeChild(this._el.parentNode || undefined, this._el); 74 | } 75 | } 76 | 77 | /** 78 | * Show the poster image 79 | * @public 80 | * @private 81 | * @returns {void} 82 | */ 83 | public show(): void { 84 | Utils.Dom.setStyle(this._el, 'display', ''); 85 | } 86 | 87 | /** 88 | * Hide the poster image 89 | * @public 90 | * @returns {void} 91 | */ 92 | public hide(): void { 93 | Utils.Dom.setStyle(this._el, 'display', 'none'); 94 | } 95 | 96 | /** 97 | * Resets the poster url and the background image 98 | * @public 99 | * @returns {void} 100 | */ 101 | public reset(): void { 102 | this._posterUrl = ''; 103 | Utils.Dom.setStyle(this._el, 'background-image', ''); 104 | } 105 | 106 | /** 107 | * Destroys the poster element 108 | * @public 109 | * @returns {void} 110 | */ 111 | public destroy(): void { 112 | this.reset(); 113 | this._removeEl(); 114 | } 115 | } 116 | 117 | export default PosterManager; 118 | -------------------------------------------------------------------------------- /src/utils/resize-watcher.ts: -------------------------------------------------------------------------------- 1 | import { FakeEvent } from '../event/fake-event'; 2 | import { FakeEventTarget } from '../event/fake-event-target'; 3 | import { CustomEventType } from '../event/event-type'; 4 | 5 | /** 6 | * A Factory class to create a resize observer for the player. 7 | */ 8 | class ResizeWatcher extends FakeEventTarget { 9 | private _observer?: ResizeObserver | IFrameObserver; 10 | private _el?: HTMLElement; 11 | 12 | constructor() { 13 | super(); 14 | } 15 | 16 | /** 17 | * Removes resize listeners. 18 | * @returns {void} 19 | */ 20 | public destroy(): void { 21 | if (this._observer) { 22 | this._observer.disconnect(); 23 | } 24 | this._observer = undefined; 25 | this._el = undefined; 26 | } 27 | 28 | /** 29 | * Start listening to a resize of the element. 30 | * @param {HTMLElement} el - the element to listen to. 31 | * @returns {void} 32 | */ 33 | public init(el: HTMLElement): void { 34 | if (this._observer) return; 35 | this._el = el; 36 | window.ResizeObserver ? this._createNativeObserver() : this._createIframeObserver(); 37 | if (this._el instanceof HTMLElement && this._observer) { 38 | (this._observer as ResizeObserver | IFrameObserver).observe(this._el); 39 | } 40 | } 41 | 42 | private _createNativeObserver(): void { 43 | this._observer = new ResizeObserver((entries) => { 44 | entries.forEach(() => { 45 | this._triggerResize(); 46 | }); 47 | }); 48 | } 49 | 50 | private _createIframeObserver(): void { 51 | this._observer = new IFrameObserver(this._triggerResize.bind(this)); 52 | } 53 | 54 | private _triggerResize(): void { 55 | this.dispatchEvent(new FakeEvent(CustomEventType.RESIZE)); 56 | } 57 | } 58 | 59 | const IFRAME_CLASS_NAME: string = 'playkit-size-iframe'; 60 | 61 | /** 62 | * This class mimics the API of the ResizeObserver API (currently available only in Chrome). 63 | * Creates an empty iFrame next to the player container, which gets the dimensions of it's parent and listens to 64 | * the iframes resize event. 65 | * @param {Function} callback - the function to be called when a resize event is detected. 66 | */ 67 | class IFrameObserver { 68 | private _observersStore: { [id: number]: HTMLIFrameElement } = {}; 69 | private _onChangeCallback: () => void; 70 | 71 | constructor(callback: () => void) { 72 | this._onChangeCallback = callback; 73 | } 74 | 75 | /** 76 | * start detecting resize event 77 | * @param {HTMLElement} el - The element that is going to be resized. 78 | * @returns {void} 79 | */ 80 | public observe(el: HTMLElement): void { 81 | const iframe = this._createIframe(); 82 | const playerId = el.getAttribute('id'); 83 | this._observersStore[playerId!] = iframe; 84 | el.appendChild(iframe); 85 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 86 | // @ts-ignore 87 | iframe.contentWindow.onresize = (): void => this._onChangeCallback(); 88 | } 89 | 90 | /** 91 | * remove all resize listeners 92 | * @returns {void} 93 | */ 94 | public disconnect(): void { 95 | for (const target in this._observersStore) { 96 | const el = document.getElementById(target); 97 | const iframe = this._observersStore[target]; 98 | iframe.onresize = null; 99 | if (el) { 100 | el.removeChild(iframe); 101 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 102 | // @ts-ignore 103 | delete this._observersStore[el.getAttribute('id')]; 104 | } 105 | } 106 | } 107 | 108 | private _createIframe(): HTMLIFrameElement { 109 | const iframe = document.createElement('iframe'); 110 | iframe.className = IFRAME_CLASS_NAME; 111 | return iframe; 112 | } 113 | } 114 | 115 | export { ResizeWatcher }; 116 | -------------------------------------------------------------------------------- /src/utils/resolution.ts: -------------------------------------------------------------------------------- 1 | import {PKMediaSourceObject} from '../types'; 2 | /** 3 | * Calculates the most suitable source to the container size 4 | * @function getSuitableSourceForResolution 5 | * @param {Array} tracks - The tracks 6 | * @param {number} width - The width to calculate with 7 | * @param {number} height - The height to calculate with 8 | * @returns {Object} - The most suitable source to the container size 9 | */ 10 | function getSuitableSourceForResolution(tracks: PKMediaSourceObject[], width: number, height: number): PKMediaSourceObject | null { 11 | let mostSuitableWidth: PKMediaSourceObject| null = null; 12 | if (height && tracks) { 13 | let mostSuitableWidthTracks: PKMediaSourceObject[] = []; 14 | let minWidthDiff = Infinity; 15 | for (const track of tracks) { 16 | // first filter the most width suitable 17 | const widthDiff = Math.abs(track.width! - width); 18 | if (widthDiff < minWidthDiff) { 19 | minWidthDiff = widthDiff; 20 | mostSuitableWidthTracks = [track]; 21 | } else if (widthDiff === minWidthDiff) { 22 | mostSuitableWidthTracks.push(track); 23 | } 24 | } 25 | const videoRatio = width / height; 26 | let mostSuitableWidthAndRatioTracks = mostSuitableWidthTracks; 27 | let minRatioDiff = Infinity; 28 | for (const track of mostSuitableWidthTracks) { 29 | // filter the most ratio suitable from the width filter results 30 | if (track.height) { 31 | const ratioDiff = Math.abs(track.width / track.height - videoRatio); 32 | if (ratioDiff < minRatioDiff) { 33 | minRatioDiff = ratioDiff; 34 | mostSuitableWidthAndRatioTracks = [track]; 35 | } else if (ratioDiff === minRatioDiff) { 36 | mostSuitableWidthAndRatioTracks.push(track); 37 | } 38 | } 39 | } 40 | let maxBandwidth = 0; 41 | for (const track of mostSuitableWidthAndRatioTracks) { 42 | // select the top bitrate from the ratio filter results 43 | if (track.bandwidth > maxBandwidth || !track.bandwidth) { 44 | maxBandwidth = track.bandwidth || maxBandwidth; 45 | mostSuitableWidth = track; 46 | } 47 | } 48 | } 49 | return mostSuitableWidth; 50 | } 51 | 52 | export {getSuitableSourceForResolution}; 53 | -------------------------------------------------------------------------------- /src/utils/restrictions.ts: -------------------------------------------------------------------------------- 1 | import VideoTrack from '../track/video-track'; 2 | import {PKABRRestrictionObject} from '../types'; 3 | 4 | /** 5 | * Filter the video tracks which not in the range 6 | * @function filterVideoTracksByRestriction 7 | * @param {Array} tracks - The tracks to filter 8 | * @param {PKABRRestrictionObject} restriction - The restriction 9 | * @returns {Array} - The relevant video tracks after restrictions. 10 | */ 11 | function _filterVideoTracksByRestriction(tracks: Array, restriction: PKABRRestrictionObject): Array { 12 | const MIN_DEFAULT_VALUE = 0; 13 | const MAX_DEFAULT_VALUE = Infinity; 14 | const inRange = (x, min, max): boolean => { 15 | return x >= (min || MIN_DEFAULT_VALUE) && x <= (max || MAX_DEFAULT_VALUE); 16 | }; 17 | const {maxHeight, minHeight, maxWidth, minWidth} = restriction; 18 | if (minHeight !== MIN_DEFAULT_VALUE || minWidth !== MIN_DEFAULT_VALUE || maxHeight !== MAX_DEFAULT_VALUE || maxWidth !== MAX_DEFAULT_VALUE) { 19 | return tracks.filter(track => inRange(track.height, minHeight, maxHeight)).filter(track => inRange(track.width, minWidth, maxWidth)); 20 | } 21 | const {maxBitrate, minBitrate} = restriction; 22 | if (minBitrate !== MIN_DEFAULT_VALUE || maxBitrate !== MAX_DEFAULT_VALUE) { 23 | return tracks.filter(track => inRange(track.bandwidth, minBitrate, maxBitrate)); 24 | } 25 | return tracks; 26 | } 27 | 28 | /** 29 | * Filter the video tracks which not in the range 30 | * @function filterVideoTracksByRestriction 31 | * @param {Array} videoTracks - The tracks to filter 32 | * @param {PKABRRestrictionObject} restriction - The restriction 33 | * @returns {Array} - The relevant video tracks after restrictions. 34 | */ 35 | function filterTracksByRestriction(videoTracks: VideoTrack[], restriction: PKABRRestrictionObject): VideoTrack[] { 36 | const filterVideoTracks = _filterVideoTracksByRestriction(videoTracks, restriction); 37 | return filterVideoTracks.length ? filterVideoTracks : []; 38 | } 39 | 40 | export {filterTracksByRestriction}; 41 | -------------------------------------------------------------------------------- /src/utils/styles.ts: -------------------------------------------------------------------------------- 1 | import * as Utils from './util'; 2 | 3 | /** 4 | * The text style class name. 5 | * @type {string} 6 | * @const 7 | */ 8 | const SUBTITLES_STYLE_CLASS_NAME: string = 'playkit-subtitles-style'; 9 | 10 | export const getSubtitleStyleSheet = (playerId: string): CSSStyleSheet => { 11 | let element = Utils.Dom.getElementBySelector(`.${playerId}.${SUBTITLES_STYLE_CLASS_NAME}`); 12 | if (!element) { 13 | element = Utils.Dom.createElement('style'); 14 | Utils.Dom.addClassName(element, playerId); 15 | Utils.Dom.addClassName(element, SUBTITLES_STYLE_CLASS_NAME); 16 | Utils.Dom.appendChild(document.head, element); 17 | } 18 | return element.sheet; 19 | }; 20 | 21 | export const resetSubtitleStyleSheet = (playerId: string): void => { 22 | const element = Utils.Dom.getElementBySelector(`.${playerId}.${SUBTITLES_STYLE_CLASS_NAME}`); 23 | element?.remove(); 24 | }; 25 | -------------------------------------------------------------------------------- /tests/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "plugin:mocha/recommended" 4 | ], 5 | "plugins": [ 6 | "mocha" 7 | ], 8 | "globals": { 9 | "sinon": true, 10 | "__VERSION__": true, 11 | "__NAME__": true 12 | }, 13 | "rules": { 14 | "mocha/max-top-level-suites": "off", 15 | "mocha/no-skipped-tests": "off", 16 | "mocha/no-setup-in-describe": "off", 17 | "mocha/no-nested-tests": "off", 18 | "mocha/no-sibling-hooks": "off", 19 | "mocha/no-exclusive-tests": "error", 20 | "mocha/no-mocha-arrows": "off", 21 | "max-len": ["warn", { "code": 50000 }] 22 | } 23 | } -------------------------------------------------------------------------------- /tests/assets/00000002.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaltura/playkit-js/385bb5594a68d5cf7d3a7ba3f7b4bd43f7073718/tests/assets/00000002.jpg -------------------------------------------------------------------------------- /tests/assets/00000003.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaltura/playkit-js/385bb5594a68d5cf7d3a7ba3f7b4bd43f7073718/tests/assets/00000003.jpg -------------------------------------------------------------------------------- /tests/assets/00000004.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaltura/playkit-js/385bb5594a68d5cf7d3a7ba3f7b4bd43f7073718/tests/assets/00000004.jpg -------------------------------------------------------------------------------- /tests/assets/00000005.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaltura/playkit-js/385bb5594a68d5cf7d3a7ba3f7b4bd43f7073718/tests/assets/00000005.jpg -------------------------------------------------------------------------------- /tests/assets/00000006.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaltura/playkit-js/385bb5594a68d5cf7d3a7ba3f7b4bd43f7073718/tests/assets/00000006.jpg -------------------------------------------------------------------------------- /tests/assets/00000007.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaltura/playkit-js/385bb5594a68d5cf7d3a7ba3f7b4bd43f7073718/tests/assets/00000007.jpg -------------------------------------------------------------------------------- /tests/assets/audios.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaltura/playkit-js/385bb5594a68d5cf7d3a7ba3f7b4bd43f7073718/tests/assets/audios.mp4 -------------------------------------------------------------------------------- /tests/assets/bbb-sprite.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaltura/playkit-js/385bb5594a68d5cf7d3a7ba3f7b4bd43f7073718/tests/assets/bbb-sprite.jpeg -------------------------------------------------------------------------------- /tests/assets/en.vtt: -------------------------------------------------------------------------------- 1 | WEBVTT 2 | 3 | 1 4 | 00:00:00.000 --> 00:00:01.000 5 | one 6 | 7 | 2 8 | 00:00:02.000 --> 00:00:03.000 9 | two 10 | 11 | 3 12 | 00:00:03.000 --> 00:00:04.000 13 | three 14 | 15 | 4 16 | 00:00:04.000 --> 00:00:05.000 17 | four 18 | 19 | 5 20 | 00:00:05.000 --> 00:00:06.000 21 | five 22 | -------------------------------------------------------------------------------- /tests/assets/he.vtt: -------------------------------------------------------------------------------- 1 | WEBVTT 2 | 3 | 1 4 | 00:00:00.000 --> 00:00:01.000 5 | אחד 6 | 7 | 2 8 | 00:00:02.000 --> 00:00:03.000 9 | שתים 10 | 11 | 3 12 | 00:00:03.000 --> 00:00:04.000 13 | שלוש 14 | 15 | 4 16 | 00:00:04.000 --> 00:00:05.000 17 | ארבע 18 | 19 | 5 20 | 00:00:05.000 --> 00:00:06.000 21 | חמש 22 | -------------------------------------------------------------------------------- /tests/assets/heb.vtt: -------------------------------------------------------------------------------- 1 | WEBVTT 2 | 3 | 1 4 | 00:00:01.000 --> 00:00:03.000 5 | דוגמה לקובץ כתוביות 6 | 7 | 2 8 | 00:00:07.000 --> 00:00:10.000 9 | 2 דוגמה לקובץ כתוביות 10 | 11 | 3 12 | 00:00:13.000 --> 00:00:16.000 13 | 3 דוגמה לקובץ כתוביות 14 | 15 | 4 16 | 00:00:20.000 --> 00:00:23.000 17 | 4 דוגמה לקובץ כתוביות 18 | 19 | 5 20 | 00:00:27.000 --> 00:00:32.000 21 | 5 דוגמה לקובץ כתוביות 22 | 23 | 6 24 | 00:00:35.000 --> 00:00:39.000 25 | 6 דוגמה לקובץ כתוביות 26 | 27 | 7 28 | 00:00:44.000 --> 00:00:47.000 29 | 7 דוגמה לקובץ כתוביות 30 | 31 | 8 32 | 00:00:52.000 --> 00:00:56.000 33 | 8 דוגמה לקובץ כתוביות 34 | 35 | 9 36 | 00:01:02.000 --> 00:01:06.000 37 | 9 דוגמה לקובץ כתוביות 38 | 39 | 10 40 | 00:01:50.000 --> 00:02:30.000 41 | דוגמה לקובץ כתוביות 10 42 | 43 | 11 44 | 00:02:50.000 --> 00:03:30.000 45 | דוגמה לקובץ כתוביות 11 46 | 47 | 12 48 | 00:03:50.000 --> 00:04:30.000 49 | דוגמה לקובץ כתוביות 12 50 | 51 | 13 52 | 00:04:50.000 --> 00:05:30.000 53 | דוגמה לקובץ כתוביות 13 54 | 55 | 14 56 | 00:05:50.000 --> 00:06:30.000 57 | דוגמה לקובץ כתוביות 14 58 | 59 | 15 60 | 00:06:50.000 --> 00:07:30.000 61 | דוגמה לקובץ כתוביות 15 62 | 63 | 16 64 | 00:07:50.000 --> 00:08:30.000 65 | דוגמה לקובץ כתוביות 16 66 | 67 | 17 68 | 00:08:50.000 --> 00:09:30.000 69 | דוגמה לקובץ כתוביות 17 -------------------------------------------------------------------------------- /tests/assets/mov_bbb.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaltura/playkit-js/385bb5594a68d5cf7d3a7ba3f7b4bd43f7073718/tests/assets/mov_bbb.mp4 -------------------------------------------------------------------------------- /tests/assets/rus.vtt: -------------------------------------------------------------------------------- 1 | WEBVTT 2 | 3 | 1 4 | 00:00:01.000 --> 00:00:03.000 5 | Пример файла субтитров 6 | 7 | 2 8 | 00:00:07.000 --> 00:00:10.000 9 | Пример файла субтитров 2 10 | 11 | 3 12 | 00:00:13.000 --> 00:00:16.000 13 | Пример файла субтитров 3 14 | 15 | 4 16 | 00:00:20.000 --> 00:00:23.000 17 | Пример файла субтитров 4 18 | 19 | 5 20 | 00:00:27.000 --> 00:00:32.000 21 | Пример файла субтитров 5 22 | 23 | 6 24 | 00:00:35.000 --> 00:00:39.000 25 | Пример файла субтитров 6 26 | 27 | 7 28 | 00:00:44.000 --> 00:00:47.000 29 | Пример файла субтитров 7 30 | 31 | 8 32 | 00:00:52.000 --> 00:00:56.000 33 | Пример файла субтитров 8 34 | 35 | 9 36 | 00:01:02.000 --> 00:01:06.000 37 | Пример файла субтитров 9 38 | 39 | 10 40 | 00:01:50.000 --> 00:02:30.000 41 | Пример файла субтитров 10 42 | 43 | 11 44 | 00:02:50.000 --> 00:03:30.000 45 | Пример файла субтитров 11 46 | 47 | 12 48 | 00:03:50.000 --> 00:04:30.000 49 | Пример файла субтитров 12 50 | 51 | 13 52 | 00:04:50.000 --> 00:05:30.000 53 | Пример файла субтитров 13 54 | 55 | 14 56 | 00:05:50.000 --> 00:06:30.000 57 | Пример файла субтитров 14 58 | 59 | 15 60 | 00:06:50.000 --> 00:07:30.000 61 | Пример файла субтитров 15 62 | 63 | 16 64 | 00:07:50.000 --> 00:08:30.000 65 | Пример файла субтитров 16 66 | 67 | 17 68 | 00:08:50.000 --> 00:09:30.000 69 | Пример файла субтитров 17 70 | -------------------------------------------------------------------------------- /tests/assets/thumbnails1.vtt: -------------------------------------------------------------------------------- 1 | WEBVTT 2 | 3 | 00:00:00.000 --> 00:00:05.000 4 | 00000002.jpg 5 | 6 | 00:00:05.000 --> 00:00:10.000 7 | 00000003.jpg 8 | 9 | 00:00:10.000 --> 00:00:15.000 10 | 00000004.jpg 11 | 12 | 00:00:15.000 --> 00:00:20.000 13 | 00000005.jpg 14 | 15 | 00:00:20.000 --> 00:00:25.000 16 | 00000006.jpg 17 | 18 | 00:00:25.000 --> 00:00:30.000 19 | 00000007.jpg 20 | -------------------------------------------------------------------------------- /tests/assets/thumbnails2.vtt: -------------------------------------------------------------------------------- 1 | WEBVTT 2 | 3 | 00:00.000 --> 00:05.000 4 | 00000002.jpg#wh=200,80 5 | 6 | 00:05.000 --> 00:10.000 7 | 00000003.jpg#wh=200,80 8 | 9 | 00:10.000 --> 00:15.000 10 | 00000004.jpg#wh=200,80 11 | 12 | 00:15.000 --> 00:20.000 13 | 00000005.jpg#wh=200,80 14 | 15 | 00:20.000 --> 00:25.000 16 | 00000006.jpg#wh=200,80 17 | -------------------------------------------------------------------------------- /tests/assets/thumbnails3.vtt: -------------------------------------------------------------------------------- 1 | WEBVTT 2 | 3 | 00:00.000 --> 00:05.000 4 | bbb-sprite.jpeg#xywh=0,0,128,72 5 | 6 | 00:05.000 --> 00:10.000 7 | bbb-sprite.jpeg#xywh=128,0,128,72 8 | 9 | 00:10.000 --> 00:15.000 10 | bbb-sprite.jpeg#xywh=256,0,128,72 11 | 12 | 00:15.000 --> 00:20.000 13 | bbb-sprite.jpeg#xywh=384,0,128,72 14 | 15 | 00:20.000 --> 00:25.000 16 | bbb-sprite.jpeg#xywh=512,0,128,72 17 | 18 | 00:25.000 --> 00:30.000 19 | bbb-sprite.jpeg#xywh=640,0,128,72 20 | 21 | 00:30.000 --> 00:35.000 22 | bbb-sprite.jpeg#xywh=768,0,128,72 23 | 24 | 00:35.000 --> 00:40.000 25 | bbb-sprite.jpeg#xywh=896,0,128,72 26 | 27 | 00:40.000 --> 00:45.000 28 | bbb-sprite.jpeg#xywh=1024,0,128,72 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /tests/configs/external-captions.json: -------------------------------------------------------------------------------- 1 | { 2 | "He": { 3 | "url": "/base/tests/assets/heb.vtt", 4 | "label": "Heb", 5 | "language": "he", 6 | "type": "vtt", 7 | "default": false 8 | }, 9 | "Ru": { 10 | "url": "/base/tests/assets/rus.vtt", 11 | "label": "Rus", 12 | "language": "ru", 13 | "type": "vtt", 14 | "default": false 15 | }, 16 | "En": { 17 | "url": "/base/tests/assets/en.vtt", 18 | "label": "Eng", 19 | "language": "En", 20 | "type": "vtt", 21 | "default": false 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/configs/sources.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hls": { 3 | "hls": [ 4 | { 5 | "mimetype": "application/x-mpegurl", 6 | "url": "http://cdnbakmi.kaltura.com/p/243342/sp/24334200/playManifest/entryId/0_uka1msg4/flavorIds/1_vqhfu6uy,1_80sohj7p/format/applehttp/protocol/http/a.m3u8" 7 | } 8 | ] 9 | }, 10 | "Mp4": { 11 | "progressive": [ 12 | { 13 | "mimetype": "video/mp4", 14 | "url": "/base/tests/assets/audios.mp4", 15 | "id": "1_rsrdfext_10081,url" 16 | } 17 | ] 18 | }, 19 | "MultipleSources": { 20 | "progressive": [ 21 | { 22 | "mimetype": "video/mp4", 23 | "url": "/base/tests/assets/audios.mp4", 24 | "id": "id1", 25 | "width": 200, 26 | "height": 100, 27 | "bandwidth": 100000, 28 | "label": "label1" 29 | }, 30 | { 31 | "mimetype": "video/mp4", 32 | "url": "/base/tests/assets/mov_bbb.mp4", 33 | "id": "id2", 34 | "width": 100, 35 | "height": 50, 36 | "bandwidth": 200000, 37 | "label": "label2" 38 | } 39 | ] 40 | }, 41 | "UnknownMimetype": { 42 | "progressive": [ 43 | { 44 | "mimetype": "someMimeType", 45 | "url": "some/url", 46 | "id": "someId" 47 | } 48 | ] 49 | }, 50 | "CorruptedUrl": { 51 | "progressive": [ 52 | { 53 | "mimetype": "video/mp4", 54 | "url": "some/corrupted/url", 55 | "id": "1_rsrdfext_10081,url" 56 | } 57 | ] 58 | }, 59 | "Live": { 60 | "hls": [ 61 | { 62 | "mimetype": "application/x-mpegurl", 63 | "url": "http://wms.shared.streamshow.it/carinatv/carinatv/playlist.m3u8" 64 | } 65 | ] 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /tests/e2e/drm/drm-scheme.spec.js: -------------------------------------------------------------------------------- 1 | import {DrmScheme} from '../../../src/drm/drm-scheme'; 2 | 3 | describe('DrmScheme', () => { 4 | it('should equal all possible drm schemes', () => { 5 | DrmScheme.should.deep.equals({ 6 | WIDEVINE: 'com.widevine.alpha', 7 | PLAYREADY: 'com.microsoft.playready', 8 | FAIRPLAY: 'com.apple.fairplay' 9 | }); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /tests/e2e/drm/fairplay.spec.js: -------------------------------------------------------------------------------- 1 | import FairPlay from '../../../src/drm/fairplay'; 2 | import {DrmScheme} from '../../../src/drm/drm-scheme'; 3 | 4 | const fpDrmData = [{licenseUrl: 'LICENSE_URL', scheme: DrmScheme.FAIRPLAY}]; 5 | const wwDrmData = [{licenseUrl: 'LICENSE_URL', scheme: DrmScheme.WIDEVINE}]; 6 | 7 | describe('FairPlay', function () { 8 | describe('isConfigured', function () { 9 | it('should return true for fairplay data if configured', function () { 10 | FairPlay.isConfigured(fpDrmData, {keySystem: DrmScheme.FAIRPLAY}).should.be.true; 11 | }); 12 | 13 | it('should return false for fairplay data if not configured', function () { 14 | FairPlay.isConfigured(fpDrmData, {keySystem: DrmScheme.WIDEVINE}).should.be.false; 15 | }); 16 | 17 | it('should return false for non-fairplay data even configured', function () { 18 | FairPlay.isConfigured(wwDrmData, {keySystem: DrmScheme.FAIRPLAY}).should.be.false; 19 | }); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /tests/e2e/engines/engine-deorator.spec.js: -------------------------------------------------------------------------------- 1 | import {FakeDecoratorProvider, FakeDecoratorProviderActive, FakeHTML5Engine} from './test-engine-decorator-providers'; 2 | import {EngineDecorator} from '../../../src/engines/engine-decorator'; 3 | import Player from '../../../src/player'; 4 | import {createElement, getConfigStructure} from '../../utils/test-utils'; 5 | import {EngineDecoratorManager} from '../../../src/engines/engine-decorator-manager'; 6 | 7 | describe('EngineDecorator', () => { 8 | let engine; 9 | beforeEach(() => { 10 | engine = new FakeHTML5Engine(); 11 | }); 12 | afterEach(() => { 13 | engine = null; 14 | }); 15 | 16 | it('should decorator be able to register once for same plugin name', () => { 17 | const decoratorManager = new EngineDecoratorManager(); 18 | decoratorManager.register(FakeDecoratorProviderActive); 19 | decoratorManager.register(FakeDecoratorProviderActive); 20 | decoratorManager.register(FakeDecoratorProviderActive); 21 | decoratorManager.createDecorators(null, null).length.should.equal(1); 22 | }); 23 | 24 | it('should decorator use the engine for non implemented methods on active decorator', () => { 25 | const decoratorManager = new EngineDecoratorManager(); 26 | decoratorManager.register(FakeDecoratorProviderActive); 27 | const engineDecorator = new EngineDecorator(engine, decoratorManager); 28 | engineDecorator.isAdaptiveBitrateEnabled().should.be.true; 29 | }); 30 | 31 | it('should decorator use the engine for implemented methods on non active decorator', () => { 32 | const decoratorManager = new EngineDecoratorManager(); 33 | decoratorManager.register(FakeDecoratorProvider); 34 | const engineDecorator = new EngineDecorator(engine, decoratorManager); 35 | engineDecorator.isLive().should.be.false; 36 | }); 37 | 38 | it('should decorator use the decorator for implemented methods on active decorator', () => { 39 | let engineDecorator, decoratorManager; 40 | decoratorManager = new EngineDecoratorManager(); 41 | decoratorManager.register(FakeDecoratorProviderActive); 42 | engineDecorator = new EngineDecorator(engine, decoratorManager); 43 | engineDecorator.isLive().should.be.true; 44 | 45 | decoratorManager = new EngineDecoratorManager(); 46 | decoratorManager.register(FakeDecoratorProvider); 47 | engineDecorator = new EngineDecorator(engine, decoratorManager); 48 | engineDecorator.isLive().should.be.false; 49 | }); 50 | 51 | it('should decorator providers should destroy on destroy', () => { 52 | const targetId = 'player-placeholder_engine-decorator.spec'; 53 | const playerContainer = createElement('DIV', targetId); 54 | 55 | const player = new Player(getConfigStructure()); 56 | player.registerEngineDecoratorProvider(FakeDecoratorProviderActive); 57 | player.registerEngineDecoratorProvider(FakeDecoratorProvider); 58 | playerContainer.appendChild(player.getView()); 59 | 60 | player.destroy(); 61 | player._engineDecoratorManager.createDecorators(null, null).length.should.equal(0); 62 | }); 63 | 64 | it('should decorator use the decorator instance as context of the function', done => { 65 | const decoratorManager = new EngineDecoratorManager(); 66 | const decoratorProvider = FakeDecoratorProviderActive; 67 | decoratorManager.register(decoratorProvider); 68 | const engineDecorator = new EngineDecorator(engine, decoratorManager); 69 | const loadPromise = engineDecorator.load(); 70 | loadPromise.then(context => { 71 | try { 72 | (context === decoratorProvider.getEngineDecorator()).should.be.true; 73 | done(); 74 | } catch (e) { 75 | done(e); 76 | } 77 | }); 78 | }); 79 | 80 | it('should decorator use the engine when decorator not active', done => { 81 | const decoratorManager = new EngineDecoratorManager(); 82 | decoratorManager.register(FakeDecoratorProvider); 83 | const engineDecorator = new EngineDecorator(engine, decoratorManager); 84 | const loadPromise = engineDecorator.load(); 85 | loadPromise.then(context => { 86 | try { 87 | (context === engine).should.be.true; 88 | done(); 89 | } catch (e) { 90 | done(e); 91 | } 92 | }); 93 | }); 94 | }); 95 | -------------------------------------------------------------------------------- /tests/e2e/engines/engine-provider.spec.js: -------------------------------------------------------------------------------- 1 | import {EngineProvider} from '../../../src/engines/engine-provider'; 2 | import {FakeFLASHEngine, FakeHTML5Engine, FakeSLEngine} from './test-engines'; 3 | import Html5 from '../../../src/engines/html5/html5'; 4 | 5 | describe('EngineProvider:register', () => { 6 | afterEach(() => { 7 | EngineProvider.destroy(); 8 | }); 9 | 10 | after(() => { 11 | EngineProvider.destroy(); 12 | EngineProvider.register(Html5.id, Html5); 13 | }); 14 | 15 | it('should register Engine', () => { 16 | Object.keys(EngineProvider.getEngines()).length.should.equal(1); 17 | EngineProvider.register(FakeFLASHEngine.id, FakeFLASHEngine); 18 | EngineProvider.getEngines().length.should.equal(2); 19 | EngineProvider.getEngines()[1].id.should.equal('Flash'); 20 | }); 21 | 22 | it('should not register Engine twice', () => { 23 | Object.keys(EngineProvider._engineProviders).length.should.equal(0); 24 | EngineProvider.register(FakeFLASHEngine.id, FakeFLASHEngine); 25 | EngineProvider.register(FakeFLASHEngine.id, FakeFLASHEngine); 26 | Object.keys(EngineProvider._engineProviders).length.should.equal(1); 27 | Object.keys(EngineProvider._engineProviders)[0].should.equal('Flash'); 28 | }); 29 | 30 | it('should register Engine and Engine2', () => { 31 | Object.keys(EngineProvider._engineProviders).length.should.equal(0); 32 | EngineProvider.register(FakeHTML5Engine.id, FakeHTML5Engine); 33 | EngineProvider.register(FakeFLASHEngine.id, FakeFLASHEngine); 34 | Object.keys(EngineProvider._engineProviders).length.should.equal(2); 35 | Object.keys(EngineProvider._engineProviders)[0].should.equal('Html5'); 36 | Object.keys(EngineProvider._engineProviders)[1].should.equal('Flash'); 37 | }); 38 | 39 | it('should not register null', () => { 40 | Object.keys(EngineProvider._engineProviders).length.should.equal(0); 41 | EngineProvider.register(null, null); 42 | Object.keys(EngineProvider._engineProviders).length.should.equal(0); 43 | }); 44 | 45 | it('should not register undefined', () => { 46 | Object.keys(EngineProvider._engineProviders).length.should.equal(0); 47 | EngineProvider.register(); 48 | Object.keys(EngineProvider._engineProviders).length.should.equal(0); 49 | }); 50 | }); 51 | 52 | describe('EngineProvider:unRegister', () => { 53 | beforeEach(() => { 54 | EngineProvider.destroy(); 55 | EngineProvider.register(FakeHTML5Engine.id, FakeHTML5Engine); 56 | EngineProvider.register(FakeFLASHEngine.id, FakeFLASHEngine); 57 | }); 58 | after(() => { 59 | EngineProvider.unRegister(FakeFLASHEngine.id); 60 | }); 61 | 62 | it('should unRegister Engine', () => { 63 | Object.keys(EngineProvider._engineProviders).length.should.equal(2); 64 | EngineProvider.unRegister(FakeFLASHEngine.id); 65 | Object.keys(EngineProvider._engineProviders).length.should.equal(1); 66 | Object.keys(EngineProvider._engineProviders)[0].should.equal('Html5'); 67 | }); 68 | 69 | it('should unRegister Engine and Engine2', () => { 70 | Object.keys(EngineProvider._engineProviders).length.should.equal(2); 71 | EngineProvider.unRegister(FakeHTML5Engine.id); 72 | EngineProvider.unRegister(FakeFLASHEngine.id); 73 | Object.keys(EngineProvider._engineProviders).length.should.equal(0); 74 | }); 75 | 76 | it('should do nothing for Engine3', () => { 77 | Object.keys(EngineProvider._engineProviders).length.should.equal(2); 78 | EngineProvider.unRegister(FakeSLEngine.id); 79 | Object.keys(EngineProvider._engineProviders).length.should.equal(2); 80 | }); 81 | 82 | it('should do nothing for null', () => { 83 | Object.keys(EngineProvider._engineProviders).length.should.equal(2); 84 | EngineProvider.unRegister(null); 85 | Object.keys(EngineProvider._engineProviders).length.should.equal(2); 86 | }); 87 | 88 | it('should do nothing for undefined', () => { 89 | Object.keys(EngineProvider._engineProviders).length.should.equal(2); 90 | EngineProvider.unRegister(); 91 | Object.keys(EngineProvider._engineProviders).length.should.equal(2); 92 | }); 93 | }); 94 | -------------------------------------------------------------------------------- /tests/e2e/engines/html5/media-source/adapters/test-adapters/test-adapters.ts: -------------------------------------------------------------------------------- 1 | import { FakeEventTarget } from '../../../../../../../src/event/fake-event-target'; 2 | import {IMediaSourceAdapter} from '../../../../../../../src/types'; 3 | import {PKDrmConfigObject} from '../../../../../../../lib/types'; 4 | 5 | class FakeNativeAdapter implements IMediaSourceAdapter { 6 | static get id() { 7 | return 'NativeAdapter'; 8 | } 9 | } 10 | 11 | class FakeHlsAdapter implements IMediaSourceAdapter { 12 | static get id() { 13 | return 'HlsAdapter'; 14 | } 15 | } 16 | 17 | class FakeDashAdapter extends FakeEventTarget implements IMediaSourceAdapter { 18 | static get id() { 19 | return 'DashAdapter'; 20 | } 21 | } 22 | 23 | class Adapter1 implements IMediaSourceAdapter { 24 | static get id() { 25 | return 'Adapter1'; 26 | } 27 | 28 | static canPlayType(mimeType: string): boolean { 29 | return ['mimeType0', 'mimeType1'].includes(mimeType); 30 | } 31 | 32 | static canPlayDrm(drmData: Array, drmConfig: PKDrmConfigObject): boolean { 33 | return !!((drmData.length && drmData[0].scheme === 's1') || drmConfig.keySystem === 's1'); 34 | } 35 | 36 | static createAdapter(videoElement: HTMLVideoElement, source: Object, config: Object): IMediaSourceAdapter { 37 | return new this(videoElement, source, config); 38 | } 39 | 40 | constructor() {} 41 | 42 | load(): void {} 43 | 44 | destroy(): void {} 45 | } 46 | 47 | class Adapter2 implements IMediaSourceAdapter { 48 | static get id() { 49 | return 'Adapter2'; 50 | } 51 | 52 | static canPlayType(mimeType: string): boolean { 53 | return ['mimeType1', 'mimeType2'].includes(mimeType); 54 | } 55 | 56 | static canPlayDrm(): boolean { 57 | return false; 58 | } 59 | 60 | static createAdapter(videoElement: HTMLVideoElement, source: Object, config: Object): IMediaSourceAdapter { 61 | return new this(videoElement, source, config); 62 | } 63 | 64 | constructor() {} 65 | 66 | load(): void {} 67 | 68 | destroy(): void {} 69 | } 70 | 71 | class Adapter3 implements IMediaSourceAdapter { 72 | static get id() { 73 | return 'Adapter3'; 74 | } 75 | 76 | static canPlayType(mimeType: string): boolean { 77 | return !!document.createElement('video').canPlayType(mimeType); 78 | } 79 | 80 | static canPlayDrm(drmData: Array, drmConfig: PKDrmConfigObject): boolean { 81 | return !!((drmData.length && drmData[0].scheme === 's3') || drmConfig.keySystem === 's3'); 82 | } 83 | 84 | static createAdapter(videoElement: HTMLVideoElement, source: Object, config: Object): IMediaSourceAdapter { 85 | return new this(videoElement, source, config); 86 | } 87 | 88 | constructor() {} 89 | 90 | load(): void {} 91 | 92 | destroy(): void {} 93 | } 94 | 95 | export {Adapter1, Adapter2, Adapter3, FakeDashAdapter, FakeHlsAdapter, FakeNativeAdapter}; 96 | -------------------------------------------------------------------------------- /tests/e2e/engines/test-engine-decorator-providers.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import {IEngine, IEngineDecorator} from '../../../src/types'; 3 | import { FakeEventTarget } from '../../../src/event/fake-event-target'; 4 | 5 | class FakeHTML5Engine extends FakeEventTarget implements IEngine { 6 | constructor() { 7 | super(); 8 | } 9 | load(): Promise { 10 | return Promise.resolve(this); 11 | } 12 | isAdaptiveBitrateEnabled() { 13 | return true; 14 | } 15 | 16 | isLive(): boolean { 17 | return false; 18 | } 19 | 20 | destroy() {} 21 | } 22 | 23 | const FakeDecoratorProvider = { 24 | getEngineDecorator: () => { 25 | return new (class EngineDecorator implements IEngineDecorator { 26 | constructor() {} 27 | 28 | get active(): boolean { 29 | return false; 30 | } 31 | })(); 32 | }, 33 | getName: () => { 34 | return 'fake'; 35 | } 36 | }; 37 | 38 | const FakeDecoratorProviderActive = { 39 | _decorator: new (class EngineDecorator implements IEngineDecorator { 40 | constructor() {} 41 | 42 | load(): Promise { 43 | return Promise.resolve(this); 44 | } 45 | 46 | isLive(): boolean { 47 | return true; 48 | } 49 | 50 | get active(): boolean { 51 | return true; 52 | } 53 | })(), 54 | getEngineDecorator: () => { 55 | return FakeDecoratorProviderActive._decorator; 56 | }, 57 | getName: () => { 58 | return 'fakeActive'; 59 | } 60 | }; 61 | 62 | export {FakeDecoratorProvider, FakeDecoratorProviderActive, FakeHTML5Engine}; 63 | -------------------------------------------------------------------------------- /tests/e2e/engines/test-engines.ts: -------------------------------------------------------------------------------- 1 | import {IEngine} from '../../../src/types'; 2 | 3 | class FakeHTML5Engine implements IEngine { 4 | static get id() { 5 | return 'Html5'; 6 | } 7 | } 8 | 9 | class FakeFLASHEngine implements IEngine { 10 | static get id() { 11 | return 'Flash'; 12 | } 13 | } 14 | 15 | class FakeSLEngine implements IEngine { 16 | static get id() { 17 | return 'SL'; 18 | } 19 | } 20 | 21 | export {FakeHTML5Engine, FakeFLASHEngine, FakeSLEngine}; 22 | -------------------------------------------------------------------------------- /tests/e2e/error/error.spec.js: -------------------------------------------------------------------------------- 1 | import Error from '../../../src/error/error'; 2 | 3 | describe('Error', () => { 4 | let error = null; 5 | 6 | it('shoud create a new error, for instance', () => { 7 | error = new Error(Error.Severity.CRITICAL, Error.Category.MANIFEST, Error.Code.UNABLE_TO_GUESS_MANIFEST_TYPE); 8 | error.code.should.equal(4000); 9 | error.category.should.equal(4); 10 | error.severity.should.equal(2); 11 | }); 12 | 13 | it('shoud create a new error, for instance', () => { 14 | error = new Error(Error.Severity.RECOVERABLE, Error.Category.NETWORK, Error.Code.BAD_HTTP_STATUS, 'kaltura.com'); 15 | error.code.should.equal(1001); 16 | error.category.should.equal(1); 17 | error.severity.should.equal(1); 18 | error.data.should.equal('kaltura.com'); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /tests/e2e/event/event-manager.spec.js: -------------------------------------------------------------------------------- 1 | import { FakeEventTarget } from '../../../src/event/fake-event-target'; 2 | import { EventManager } from '../../../src/event/event-manager'; 3 | 4 | class EventTarget extends FakeEventTarget {} 5 | 6 | describe('unlisten', function () { 7 | let eventTarget, eventManager; 8 | 9 | beforeEach(() => { 10 | eventTarget = new EventTarget(); 11 | eventManager = new EventManager(); 12 | }); 13 | 14 | afterEach(() => { 15 | eventManager.removeAll(); 16 | }); 17 | 18 | it('should remove all listeners for no specific listener given', done => { 19 | let listener1 = () => { 20 | done(new Error('test fail')); 21 | }; 22 | let listener2 = () => { 23 | done(new Error('test fail')); 24 | }; 25 | eventManager.listen(eventTarget, 'event', listener1); 26 | eventManager.listen(eventTarget, 'event', listener2); 27 | eventManager.unlisten(eventTarget, 'event'); 28 | eventManager._bindingMap.get('event').length.should.equal(0); 29 | setTimeout(() => { 30 | done(); 31 | }, 0); 32 | }); 33 | 34 | it('should remove only the given listeners', done => { 35 | let listener1 = () => { 36 | done(new Error('test fail')); 37 | }; 38 | let listener2 = () => { 39 | done(); 40 | }; 41 | eventManager.listen(eventTarget, 'event', listener1); 42 | eventManager.listen(eventTarget, 'event', listener2); 43 | eventManager.unlisten(eventTarget, 'event', listener1); 44 | eventManager._bindingMap.get('event').length.should.equal(1); 45 | eventManager._bindingMap.get('event')[0].listener.should.equal(listener2); 46 | eventTarget.dispatchEvent({type: 'event'}); 47 | }); 48 | }); 49 | 50 | describe('one', function () { 51 | let eventTarget, eventManager, listener1, listener2; 52 | 53 | beforeEach(() => { 54 | eventTarget = new EventTarget(); 55 | eventManager = new EventManager(); 56 | }); 57 | 58 | afterEach(() => { 59 | eventManager.removeAll(); 60 | }); 61 | 62 | it('should listen only one time', done => { 63 | let counter = 0; 64 | listener1 = event => { 65 | counter++; 66 | event.type.should.equal('event1'); 67 | }; 68 | listener2 = event => { 69 | counter++; 70 | event.type.should.equal('event2'); 71 | }; 72 | eventManager.listenOnce(eventTarget, 'event1', listener1); 73 | eventTarget.dispatchEvent({type: 'event1'}); 74 | eventTarget.dispatchEvent({type: 'event1'}); 75 | eventManager.listenOnce(eventTarget, 'event2', listener2); 76 | eventTarget.dispatchEvent({type: 'event2'}); 77 | eventTarget.dispatchEvent({type: 'event2'}); 78 | setTimeout(() => { 79 | counter.should.equal(2); 80 | done(); 81 | }, 0); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /tests/e2e/fullscreen/fullscreen-controller.spec.js: -------------------------------------------------------------------------------- 1 | import {createElement, removeElement} from '../../utils/test-utils'; 2 | import Player from '../../../src/player'; 3 | import {Object as PKObject} from '../../../src/utils/util'; 4 | import SourcesConfig from '../../configs/sources.json'; 5 | import {EngineProvider} from '../../../src/engines/engine-provider'; 6 | import Html5 from '../../../src/engines/html5/html5'; 7 | 8 | const targetId = 'player-placeholder_inBrowserFullscreen.spec'; 9 | const sourcesConfig = PKObject.copyDeep(SourcesConfig); 10 | 11 | describe('check inBrowserFullscreen config', function () { 12 | let config, player, playerContainer, sandbox; 13 | 14 | before(() => { 15 | EngineProvider.destroy(); 16 | EngineProvider.register(Html5.id, Html5); 17 | playerContainer = createElement('DIV', targetId); 18 | config = { 19 | playback: { 20 | inBrowserFullscreen: true, 21 | playsinline: true 22 | } 23 | }; 24 | }); 25 | 26 | beforeEach(() => { 27 | sandbox = sinon.createSandbox(); 28 | player = new Player(config); 29 | player.setSources(sourcesConfig.Mp4); 30 | playerContainer.appendChild(player.getView()); 31 | }); 32 | 33 | afterEach(() => { 34 | sandbox.restore(); 35 | player.destroy(); 36 | }); 37 | 38 | after(() => { 39 | removeElement(targetId); 40 | }); 41 | 42 | it('should switch correctly to fullscreen in iOS between native and inBrowserFullscreen config', () => { 43 | sandbox.stub(player._fullscreenController, '_isIOSFullscreen').callsFake(() => { 44 | return false; 45 | }); 46 | player.env.os.name = 'iOS'; 47 | player.enterFullscreen(); 48 | player.isFullscreen().should.be.true; 49 | player._fullscreenController._isInBrowserFullscreen.should.be.true; 50 | player.configure({ 51 | playback: { 52 | inBrowserFullscreen: false, 53 | playsinline: false 54 | } 55 | }); 56 | player.isFullscreen().should.be.true; 57 | player.enterFullscreen(); 58 | player.isFullscreen().should.be.true; 59 | player.exitFullscreen(); 60 | player.isFullscreen().should.be.false; 61 | player.enterFullscreen(); 62 | sandbox.restore(); 63 | sandbox.stub(player._fullscreenController, '_isIOSFullscreen').callsFake(() => { 64 | return true; 65 | }); 66 | player.isFullscreen().should.be.true; 67 | }); 68 | 69 | it('should change fullscreen mode correctly', () => { 70 | player.configure({ 71 | playback: { 72 | inBrowserFullscreen: false, 73 | playsinline: false 74 | } 75 | }); 76 | sandbox.stub(player._fullscreenController, '_isNativeDocumentFullscreen').callsFake(() => { 77 | return true; 78 | }); 79 | // indicator for specific player if it's in fullscreen or another element in fullscreen 80 | player._fullscreenController._isElementInFullscreen = true; 81 | player.isFullscreen().should.be.true; 82 | 83 | // indicator for specific player if it's in fullscreen or another element in fullscreen 84 | player._fullscreenController._isElementInFullscreen = false; 85 | player.isFullscreen().should.be.false; 86 | 87 | sandbox.restore(); 88 | sandbox.stub(player._fullscreenController, '_isNativeDocumentFullscreen').callsFake(() => { 89 | return false; 90 | }); 91 | 92 | player._fullscreenController._isElementInFullscreen = false; 93 | player.isFullscreen().should.be.false; 94 | }); 95 | }); 96 | -------------------------------------------------------------------------------- /tests/e2e/middleware/middleware.spec.js: -------------------------------------------------------------------------------- 1 | import BaseMiddleware from '../../../src/middleware/base-middleware'; 2 | import Middleware from '../../../src/middleware/middleware'; 3 | import getLogger from '../../../src/utils/logger'; 4 | 5 | class M1 extends BaseMiddleware { 6 | id = 'M1'; 7 | logger = getLogger(this.id); 8 | 9 | drink(next) { 10 | this.logger.debug('drink'); 11 | this.callNext(next); 12 | } 13 | 14 | eat(type1, type2, next) { 15 | this.logger.debug('eat', type1, type2); 16 | this.callNext(next); 17 | } 18 | } 19 | 20 | class M2 extends BaseMiddleware { 21 | id = 'M2'; 22 | logger = getLogger(this.id); 23 | 24 | drink(next) { 25 | this.logger.debug('drink'); 26 | this.callNext(next); 27 | } 28 | 29 | eat(type1, type2, next) { 30 | this.logger.debug('eat', type1, type2); 31 | this.callNext(() => next(type2, type1)); 32 | } 33 | } 34 | 35 | class M3 extends BaseMiddleware { 36 | id = 'M3'; 37 | logger = getLogger(this.id); 38 | 39 | drink(next) { 40 | this.logger.debug('drink'); 41 | this.callNext(next); 42 | } 43 | 44 | eat(type1, type2, next) { 45 | this.logger.debug('eat', type1, type2); 46 | this.callNext(next); 47 | } 48 | } 49 | 50 | describe('Middleware', function () { 51 | let m1, m2, m3; 52 | let middleware; 53 | let actions = {DRINK: 'drink', EAT: 'eat'}; 54 | let sandbox; 55 | 56 | beforeEach(function () { 57 | middleware = new Middleware(actions); 58 | }); 59 | 60 | describe('use', function () { 61 | beforeEach(function () { 62 | m1 = new M1(); 63 | m2 = new M2(); 64 | m3 = new M3(); 65 | middleware.use(m1); 66 | middleware.use(m2); 67 | middleware.use(m3); 68 | }); 69 | 70 | it('should register the base middlewares', function () { 71 | middleware._middlewares.get(actions.DRINK).should.have.lengthOf(3); 72 | middleware._middlewares.get(actions.EAT).should.have.lengthOf(3); 73 | }); 74 | }); 75 | 76 | describe('run', function () { 77 | let spyM1, spyM2, spyM3; 78 | 79 | beforeEach(function () { 80 | sandbox = sinon.createSandbox(); 81 | }); 82 | 83 | afterEach(function () { 84 | sandbox.restore(); 85 | }); 86 | 87 | it('should run all the base middlewares for action drink', function (done) { 88 | spyM1 = sandbox.spy(M1.prototype, 'drink'); 89 | spyM2 = sandbox.spy(M2.prototype, 'drink'); 90 | spyM3 = sandbox.spy(M3.prototype, 'drink'); 91 | m1 = new M1(); 92 | m2 = new M2(); 93 | m3 = new M3(); 94 | middleware.use(m1); 95 | middleware.use(m2); 96 | middleware.use(m3); 97 | middleware.run(actions.DRINK, () => { 98 | spyM1.should.have.been.calledOnce; 99 | spyM2.should.have.been.calledOnce; 100 | spyM3.should.have.been.calledOnce; 101 | spyM2.should.have.been.calledAfter(spyM1); 102 | spyM3.should.have.been.calledAfter(spyM2); 103 | done(); 104 | }); 105 | }); 106 | 107 | it('should run all the base middlewares for action eat', function (done) { 108 | spyM1 = sandbox.spy(M1.prototype, 'eat'); 109 | spyM2 = sandbox.spy(M2.prototype, 'eat'); 110 | spyM3 = sandbox.spy(M3.prototype, 'eat'); 111 | m1 = new M1(); 112 | m2 = new M2(); 113 | m3 = new M3(); 114 | middleware.use(m1); 115 | middleware.use(m2); 116 | middleware.use(m3); 117 | middleware.run( 118 | actions.EAT, 119 | (...types) => { 120 | spyM1.should.have.been.calledOnceWith('pizza', 'pasta'); 121 | spyM2.should.have.been.calledOnceWith('pizza', 'pasta'); 122 | spyM3.should.have.been.calledOnceWith('pasta', 'pizza'); 123 | types.should.deep.equal(['pasta', 'pizza', undefined]); 124 | spyM2.should.have.been.calledAfter(spyM1); 125 | spyM3.should.have.been.calledAfter(spyM2); 126 | done(); 127 | }, 128 | 'pizza', 129 | 'pasta' 130 | ); 131 | }); 132 | 133 | it('should run only callback for un valid action', function (done) { 134 | m1 = new M1(); 135 | m2 = new M2(); 136 | m3 = new M3(); 137 | middleware.use(m1); 138 | middleware.use(m2); 139 | middleware.use(m3); 140 | middleware.run('sleep', () => { 141 | done(); 142 | }); 143 | }); 144 | }); 145 | }); 146 | -------------------------------------------------------------------------------- /tests/e2e/state/state.spec.js: -------------------------------------------------------------------------------- 1 | import State from '../../../src/state/state'; 2 | import {StateType} from '../../../src/state/state-type'; 3 | 4 | describe('State', () => { 5 | it('should create idle state', () => { 6 | let idleState = new State(StateType.IDLE); 7 | idleState.type.should.equal(StateType.IDLE); 8 | idleState._duration.should.equal(0); 9 | idleState._timestamp.should.be.a('number'); 10 | }); 11 | 12 | it('should create loading state', () => { 13 | let idleState = new State(StateType.LOADING); 14 | idleState.type.should.equal(StateType.LOADING); 15 | idleState._duration.should.equal(0); 16 | idleState._timestamp.should.be.a('number'); 17 | }); 18 | 19 | it('should create buffering state', () => { 20 | let idleState = new State(StateType.BUFFERING); 21 | idleState.type.should.equal(StateType.BUFFERING); 22 | idleState._duration.should.equal(0); 23 | idleState._timestamp.should.be.a('number'); 24 | }); 25 | 26 | it('should create playing state', () => { 27 | let idleState = new State(StateType.PLAYING); 28 | idleState.type.should.equal(StateType.PLAYING); 29 | idleState._duration.should.equal(0); 30 | idleState._timestamp.should.be.a('number'); 31 | }); 32 | 33 | it('should create paused state', () => { 34 | let idleState = new State(StateType.PAUSED); 35 | idleState.type.should.equal(StateType.PAUSED); 36 | idleState._duration.should.equal(0); 37 | idleState._timestamp.should.be.a('number'); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /tests/e2e/state/states.spec.js: -------------------------------------------------------------------------------- 1 | import {StateType} from '../../../src/state/state-type'; 2 | 3 | describe('States', () => { 4 | it('should equal possible player states', () => { 5 | StateType.should.deep.equal({ 6 | IDLE: 'idle', 7 | LOADING: 'loading', 8 | PLAYING: 'playing', 9 | PAUSED: 'paused', 10 | BUFFERING: 'buffering' 11 | }); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /tests/e2e/track/timed-metadata.spec.js: -------------------------------------------------------------------------------- 1 | import {TimedMetadata, createTextTrackCue, createTimedMetadata} from '../../../src/track/timed-metadata'; 2 | 3 | describe('TimedMetadata', () => { 4 | const info = {info: 'info'}; 5 | const params = { 6 | startTime: 10, 7 | endTime: 20, 8 | id: 'id', 9 | type: 'type', 10 | metadata: info 11 | }; 12 | describe('createTextTrackCue', () => { 13 | const {startTime, endTime, id, type, metadata} = params; 14 | const timedMetadata = new TimedMetadata(startTime, endTime, id, type, metadata); 15 | 16 | it('should create TextTrackCue based on TimedMetadata', () => { 17 | const vttCue = createTextTrackCue(timedMetadata); 18 | (vttCue instanceof window.VTTCue).should.be.true; 19 | vttCue.startTime.should.equal(startTime); 20 | vttCue.endTime.should.equal(endTime); 21 | vttCue.id.should.equal(id); 22 | vttCue.value.key.should.equal(type); 23 | vttCue.value.data.should.equal(metadata); 24 | }); 25 | 26 | it('should return null for null given', () => { 27 | const vttCue = createTextTrackCue(null); 28 | (vttCue === null).should.be.true; 29 | }); 30 | 31 | it('should return null for empty object given', () => { 32 | const vttCue = createTextTrackCue({}); 33 | (vttCue === null).should.be.true; 34 | }); 35 | }); 36 | 37 | describe('createTimedMetadata', () => { 38 | const {startTime, endTime, id, type, metadata} = params; 39 | const timedMetadata = new TimedMetadata(startTime, endTime, id, type, metadata); 40 | const vttCue = createTextTrackCue(timedMetadata); 41 | 42 | it('should create TimedMetadata based on TextTrackCue', () => { 43 | const newTimedMetadata = createTimedMetadata(vttCue); 44 | (newTimedMetadata instanceof TimedMetadata).should.be.true; 45 | newTimedMetadata.should.be.deep.equal({...timedMetadata, type: TimedMetadata.TYPE.CUSTOM}); 46 | }); 47 | 48 | it('should create TimedMetadata based on id3 tag', () => { 49 | const id3 = createTextTrackCue(timedMetadata); 50 | id3.type = 'org.id3'; 51 | const newTimedMetadata = createTimedMetadata(id3); 52 | (newTimedMetadata instanceof TimedMetadata).should.be.true; 53 | newTimedMetadata.should.be.deep.equal({...timedMetadata, metadata: {key: type, data: info}, type: TimedMetadata.TYPE.ID3}); 54 | }); 55 | 56 | it('should create TimedMetadata based cue with no value.data', () => { 57 | const cue = new window.VTTCue(startTime, endTime, ''); 58 | cue.id = id; 59 | cue.value = {key: type, ...info}; 60 | const newTimedMetadata = createTimedMetadata(cue); 61 | (newTimedMetadata instanceof TimedMetadata).should.be.true; 62 | newTimedMetadata.should.be.deep.equal({...timedMetadata, metadata: cue.value, type: TimedMetadata.TYPE.CUSTOM}); 63 | }); 64 | 65 | it('should return null for null given', () => { 66 | const newTimedMetadata = createTimedMetadata(null); 67 | (newTimedMetadata === null).should.be.true; 68 | }); 69 | 70 | it('should return null for empty object given', () => { 71 | const newTimedMetadata = createTimedMetadata({}); 72 | (newTimedMetadata === null).should.be.true; 73 | }); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /tests/e2e/track/track.spec.js: -------------------------------------------------------------------------------- 1 | import Track from '../../../src/track/text-track'; 2 | 3 | describe('Track', () => { 4 | describe('langComparer', () => { 5 | it('should compare 2 languages with same length', () => { 6 | Track.langComparer('ita', 'ita').should.be.true; 7 | Track.langComparer('ita', 'eng').should.be.false; 8 | Track.langComparer('ita', 'ItA').should.be.true; 9 | }); 10 | 11 | it('should compare 2 languages with different length', () => { 12 | Track.langComparer('ita', 'It').should.be.true; 13 | Track.langComparer('ita', 'it').should.be.true; 14 | Track.langComparer('it', 'ita').should.be.true; 15 | Track.langComparer('es', 'ita').should.be.false; 16 | Track.langComparer('ita', 'es').should.be.false; 17 | }); 18 | 19 | it('should return false if the input lang is empty', () => { 20 | Track.langComparer('', 'rus').should.be.false; 21 | }); 22 | 23 | it('should compare languages using equality flag', () => { 24 | Track.langComparer('zh', 'zh', undefined, true).should.be.true; 25 | Track.langComparer('zh_tw', 'zh', undefined, true).should.be.false; 26 | Track.langComparer('zh_tw', 'zh', undefined, false).should.be.true; 27 | }); 28 | 29 | it('should compare languages also with the additionalLanguage from the configuration', () => { 30 | Track.langComparer('zh', 'chi', 'chi', true).should.be.true; 31 | Track.langComparer('es', 'spa', 'spa', true).should.be.true; 32 | Track.langComparer('es', 'zh', 'spa', true).should.be.false; 33 | }); 34 | 35 | it('should compare languages also with the additionalLanguage as array from the configuration', () => { 36 | Track.langComparer('zh', 'chi', ['rus','chi'] , true).should.be.true; 37 | Track.langComparer('es', 'spa', ['spa','heb'], true).should.be.true; 38 | Track.langComparer('es', 'zh', ['spa','heb'], true).should.be.false; 39 | }); 40 | it('should compare languages also with the additionalLanguage as empty array from the configuration', () => { 41 | Track.langComparer('zh', 'chi', [] , true).should.be.false; 42 | Track.langComparer('es', 'es', [], true).should.be.true; 43 | Track.langComparer('es', 'zh', [], true).should.be.false; 44 | }); 45 | 46 | it('should compare languages also with the additionalLanguage as array and check startWith ', () => { 47 | Track.langComparer('zh', 'chi', ['rus','chi_tw'] , false).should.be.true; 48 | Track.langComparer('chi_tw', 'chi', ['spa','heb'], false).should.be.true; 49 | Track.langComparer('es', 'chi', ['chi_tw','heb'], false).should.be.true; 50 | Track.langComparer('es', 'chi', ['es','heb'], false).should.be.false; 51 | }); 52 | 53 | it('should compare with the additionalLanguage as empty array from the configuration', () => { 54 | Track.langComparer('', 'chi', [] , true).should.be.false; 55 | Track.langComparer('', 'chi', ['rus','chi'] , true).should.be.true; 56 | Track.langComparer('', 'spa', ['spa','heb'], true).should.be.true; 57 | Track.langComparer('', 'zh', ['spa','heb'], true).should.be.false; 58 | }); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /tests/e2e/utils/binary-search.spec.js: -------------------------------------------------------------------------------- 1 | import {binarySearch} from '../../../src/utils'; 2 | 3 | describe('binarySearch', () => { 4 | it('should find primitives', () => { 5 | binarySearch([1, 2, 3, 4, 7, 8, 9, 10], int => int - 7).should.equal(7); 6 | binarySearch([1, 2, 3, 4, 7, 8, 9, 10], int => int - 2).should.equal(2); 7 | binarySearch([0, 1, 2, 3, 4, 7, 8, 9, 10], int => int - 0).should.equal(0); 8 | binarySearch([-1, 2, 3, 4, 7, 8, 9, 10], int => int + 1).should.equal(-1); 9 | (binarySearch([-1, 2, 3, 4, 7, 8, 9, 10], int => int - 11) === null).should.be.true; 10 | }); 11 | it('should find object', () => { 12 | binarySearch([{num: 0}, {num: 10}, {num: 100}, {num: 1000}], obj => obj.num - 1000).should.deep.equal({num: 1000}); 13 | (binarySearch([{num: 0}, {num: 10}, {num: 100}, {num: 1000}], obj => obj.num - 2000) === null).should.be.true; 14 | }); 15 | it('should has default', () => { 16 | (binarySearch(undefined, int => int - 1) === null).should.be.true; 17 | (binarySearch([-1, 2, 3, 4, 7, 8, 9, 10]) === null).should.be.true; 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /tests/e2e/utils/logger.spec.js: -------------------------------------------------------------------------------- 1 | import getLogger, {LogLevel} from '../../../src/utils/logger'; 2 | 3 | describe('LoggerFactory', () => { 4 | let loggerA = null; 5 | let loggerB = null; 6 | 7 | beforeEach(() => { 8 | loggerA = getLogger('loggerA'); 9 | loggerB = getLogger('loggerB'); 10 | }); 11 | 12 | it('should create two separate loggers', () => { 13 | loggerA.context.name.should.equal('loggerA'); 14 | loggerB.context.name.should.equal('loggerB'); 15 | }); 16 | 17 | it("should change loggerA's log level", () => { 18 | loggerA.setLevel(LogLevel.WARN); 19 | loggerA.context.filterLevel.should.deep.equal(LogLevel.WARN); 20 | loggerB.context.filterLevel.should.deep.equal(LogLevel.DEBUG); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /tests/e2e/utils/poster-manager.spec.js: -------------------------------------------------------------------------------- 1 | import PosterManager from '../../../src/utils/poster-manager'; 2 | 3 | describe('PosterManager', () => { 4 | it('should create poster element', () => { 5 | const posterManager = new PosterManager(); 6 | let posterDomElement = posterManager.getElement(); 7 | posterDomElement.should.be.a('htmldivelement'); 8 | }); 9 | 10 | it('should return the poster URL', () => { 11 | const posterManager = new PosterManager(); 12 | const posterImageUrl = 'http://myTestUrl.com'; 13 | posterManager.setSrc(posterImageUrl); 14 | posterManager.src.should.be.equal(posterImageUrl); 15 | }); 16 | 17 | it('should reset the poster manager', () => { 18 | const posterManager = new PosterManager(); 19 | const posterImageUrl = 'http://myTestUrl.com'; 20 | posterManager.setSrc(posterImageUrl); 21 | posterManager.reset(); 22 | posterManager._posterUrl.should.be.empty; 23 | posterManager._el.style.backgroundImage.should.be.empty; 24 | }); 25 | 26 | it('should destroy the poster manager', () => { 27 | const posterManager = new PosterManager(); 28 | const posterImageUrl = 'http://myTestUrl.com'; 29 | posterManager.setSrc(posterImageUrl); 30 | posterManager.destroy(); 31 | posterManager._posterUrl.should.be.empty; 32 | (posterManager._el.parentNode === null).should.be.true; 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /tests/e2e/utils/resize-watcher.spec.js: -------------------------------------------------------------------------------- 1 | import {ResizeWatcher} from '../../../src/utils'; 2 | 3 | describe('ResizeWatcher', () => { 4 | let rw; 5 | 6 | beforeEach(() => { 7 | rw = new ResizeWatcher(); 8 | }); 9 | 10 | it('create a native observer', () => { 11 | if (window.ResizeObserver) { 12 | rw._createNativeObserver(); 13 | rw._observer.should.be.instanceof(window.ResizeObserver); 14 | rw.destroy(); 15 | } 16 | }); 17 | 18 | it('create a iframe observer', () => { 19 | rw._createIframeObserver(); 20 | rw._observer.should.not.be.instanceof(window.ResizeObserver); 21 | rw._observer.should.be.an('Object'); 22 | rw.destroy(); 23 | }); 24 | 25 | it('iframe observer _createIframe', () => { 26 | rw._createIframeObserver(); 27 | let iframe = rw._observer._createIframe(); 28 | iframe.should.be.instanceOf(HTMLElement); 29 | iframe.className.should.be.equal('playkit-size-iframe'); 30 | rw.destroy(); 31 | }); 32 | 33 | it('iframe observer observe', done => { 34 | rw._createIframeObserver(); 35 | let container = document.createElement('div'); 36 | container.setAttribute('id', 'testId123456789'); 37 | document.body.appendChild(container); 38 | container.style.position = 'relative'; 39 | container.style.width = '200px'; 40 | container.style.height = '200px'; 41 | rw._observer.observe(container); 42 | rw._observer._observersStore['testId123456789'].should.be.instanceof(HTMLIFrameElement); 43 | rw.addEventListener('resize', () => { 44 | rw.destroy(); 45 | document.body.removeChild(container); 46 | done(); 47 | }); 48 | setTimeout(() => { 49 | container.style.height = '300px'; 50 | }, 100); 51 | }); 52 | 53 | it('iframe observer disconnect', done => { 54 | rw._createIframeObserver(); 55 | let container = document.createElement('div'); 56 | container.setAttribute('id', 'testId123456789'); 57 | document.body.appendChild(container); 58 | container.style.position = 'relative'; 59 | container.style.width = '200px'; 60 | container.style.height = '200px'; 61 | rw._observer.observe(container); 62 | rw._observer.disconnect(); 63 | rw.addEventListener('resize', () => { 64 | document.body.removeChild(container); 65 | done('failed'); 66 | }); 67 | setTimeout(() => { 68 | document.body.removeChild(container); 69 | done(); 70 | }, 500); 71 | setTimeout(() => { 72 | container.style.height = '300px'; 73 | }, 100); 74 | }); 75 | 76 | it('trigger a resize event', done => { 77 | rw.addEventListener('resize', () => { 78 | done(); 79 | }); 80 | rw._triggerResize(); 81 | }); 82 | 83 | it('create an observer', done => { 84 | let el = document.createElement('div'); 85 | el.setAttribute('id', 'coolObserver123'); 86 | document.body.appendChild(el); 87 | rw.init(el); 88 | rw.addEventListener('resize', () => { 89 | rw.destroy(); 90 | document.body.removeChild(el); 91 | done(); 92 | }); 93 | el.style.width = '200px'; 94 | }); 95 | }); 96 | -------------------------------------------------------------------------------- /tests/e2e/utils/resolution.spec.js: -------------------------------------------------------------------------------- 1 | import {getSuitableSourceForResolution} from '../../../src/utils/resolution'; 2 | 3 | describe('getSuitableSourceForResolution', () => { 4 | let tracks = [ 5 | { 6 | width: 100 7 | }, 8 | { 9 | width: 200, 10 | height: 100 11 | }, 12 | { 13 | width: 400, 14 | height: 400, 15 | bandwidth: 20000 16 | }, 17 | { 18 | width: 800, 19 | height: 400 20 | }, 21 | { 22 | width: 800, 23 | height: 800, 24 | bandwidth: 10000 25 | }, 26 | { 27 | width: 800, 28 | height: 800, 29 | bandwidth: 20000 30 | } 31 | ]; 32 | 33 | it('should not select for no params given', () => { 34 | (getSuitableSourceForResolution() === null).should.be.true; 35 | }); 36 | 37 | it('should not select for no height given', () => { 38 | (getSuitableSourceForResolution(tracks, 100, 0) === null).should.be.true; 39 | }); 40 | 41 | it('should not select for no tracks given', () => { 42 | (getSuitableSourceForResolution(null, 100, 100) === null).should.be.true; 43 | }); 44 | 45 | it('should not select for empty tracks given', () => { 46 | (getSuitableSourceForResolution([], 100, 100) === null).should.be.true; 47 | }); 48 | 49 | it('should select according width', () => { 50 | (getSuitableSourceForResolution(tracks, 210, 210) === tracks[1]).should.be.true; 51 | (getSuitableSourceForResolution(tracks, 190, 190) === tracks[1]).should.be.true; 52 | (getSuitableSourceForResolution(tracks, 100, 100) === tracks[0]).should.be.true; 53 | }); 54 | 55 | it('should select according ratio', () => { 56 | (getSuitableSourceForResolution(tracks, 800, 500) === tracks[3]).should.be.true; 57 | (getSuitableSourceForResolution(tracks, 800, 300) === tracks[3]).should.be.true; 58 | }); 59 | 60 | it('should select according bandwidth', () => { 61 | (getSuitableSourceForResolution(tracks, 800, 700) === tracks[5]).should.be.true; 62 | (getSuitableSourceForResolution(tracks, 800, 900) === tracks[5]).should.be.true; 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /tests/index.js: -------------------------------------------------------------------------------- 1 | import { should, use } from 'chai'; 2 | import sinonChai from 'sinon-chai'; 3 | import sinon from 'sinon/pkg/sinon-esm'; 4 | import * as chaiAsPromised from 'chai-as-promised'; 5 | 6 | should(); 7 | use(sinonChai); 8 | use(chaiAsPromised); 9 | global.sinon = sinon; 10 | 11 | const testsContext = require.context('./e2e', true); 12 | testsContext.keys().forEach(testsContext); 13 | -------------------------------------------------------------------------------- /tsconfig-lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "emitDeclarationOnly": true, 5 | "declaration": true, 6 | "declarationMap": true, 7 | }, 8 | "include": ["src/**/*", "./src/types/global/*.d.ts"], 9 | "exclude": ["node_modules", "dist/**/*", "demo/libs/**/*"] 10 | } 11 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "ESNext", 5 | "baseUrl": ".", 6 | "sourceMap": true, 7 | "moduleResolution": "Node", 8 | "strict": false, 9 | "strictNullChecks": true, 10 | "strictPropertyInitialization": true, 11 | "resolveJsonModule": true, 12 | "esModuleInterop": true, 13 | "skipLibCheck": true, 14 | "types": [], 15 | "lib": [ 16 | "dom", 17 | "ESNext" 18 | ], 19 | "outDir": "./lib/", 20 | "allowSyntheticDefaultImports": true, 21 | }, 22 | "include": ["src/**/*"] 23 | } 24 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | const packageData = require('./package.json'); 4 | const {insertStylesWithNonce} = require('@playkit-js/webpack-common'); 5 | 6 | module.exports = (env, { mode }) => { 7 | return { 8 | entry: './src/playkit.ts', 9 | devtool: 'source-map', 10 | module: { 11 | rules: [ 12 | { 13 | test: /\.(ts|js)$/, 14 | exclude: /node_modules/, 15 | use: { 16 | loader: 'babel-loader', 17 | options: { 18 | presets: [ 19 | [ 20 | '@babel/preset-env', 21 | { 22 | bugfixes: true, 23 | } 24 | ], 25 | '@babel/preset-typescript' 26 | ], 27 | plugins: [['@babel/plugin-transform-runtime']] 28 | } 29 | } 30 | }, 31 | { 32 | test: /\.css$/i, 33 | use: [ 34 | { 35 | loader: "style-loader", 36 | options: { 37 | attributes: { 38 | id: `${packageData.name}` 39 | }, 40 | insert: insertStylesWithNonce 41 | } 42 | }, 43 | { 44 | loader: "css-loader", 45 | }, 46 | ] 47 | } 48 | ] 49 | }, 50 | resolve: { 51 | extensions: ['.ts', '.js'] 52 | }, 53 | output: { 54 | filename: 'playkit.js', 55 | path: path.resolve(__dirname, 'dist'), 56 | library: { 57 | umdNamedDefine: true, 58 | name: ['playkit', 'core'], 59 | type: 'umd' 60 | }, 61 | clean: true 62 | }, 63 | devServer: { 64 | static: { 65 | directory: path.join(__dirname, 'demo') 66 | }, 67 | client: { 68 | progress: true 69 | } 70 | }, 71 | plugins: [ 72 | new webpack.DefinePlugin({ 73 | __VERSION__: JSON.stringify(packageData.version), 74 | __NAME__: JSON.stringify(packageData.name) 75 | }) 76 | ] 77 | }; 78 | }; 79 | --------------------------------------------------------------------------------