Compare commits
	
		
			1 Commits
		
	
	
		
			develop
			...
			production
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 6bb6c57155 | 
| 
						 | 
					@ -2,7 +2,9 @@ test/
 | 
				
			||||||
storage/
 | 
					storage/
 | 
				
			||||||
dist/
 | 
					dist/
 | 
				
			||||||
client/dev/
 | 
					client/dev/
 | 
				
			||||||
 | 
					client/dist/
 | 
				
			||||||
client/node_modules/
 | 
					client/node_modules/
 | 
				
			||||||
 | 
					client/fluid-player/node_modules/
 | 
				
			||||||
__pycache__
 | 
					__pycache__
 | 
				
			||||||
venv
 | 
					venv
 | 
				
			||||||
.env
 | 
					.env
 | 
				
			||||||
| 
						 | 
					@ -19,3 +21,4 @@ docker-compose*
 | 
				
			||||||
node_modules
 | 
					node_modules
 | 
				
			||||||
npm-debug.log
 | 
					npm-debug.log
 | 
				
			||||||
README.md
 | 
					README.md
 | 
				
			||||||
 | 
					.history
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										4
									
								
								.flake8
									
									
									
									
									
								
							
							
						
						| 
						 | 
					@ -6,6 +6,10 @@ ignore =
 | 
				
			||||||
    F401
 | 
					    F401
 | 
				
			||||||
    # flake struggles with endpoints returning tuples on exceptions
 | 
					    # flake struggles with endpoints returning tuples on exceptions
 | 
				
			||||||
    E722
 | 
					    E722
 | 
				
			||||||
 | 
					    # apparently it's both an antipattern and a best practice:
 | 
				
			||||||
 | 
					    # https://www.flake8rules.com/rules/W503.html
 | 
				
			||||||
 | 
					    # just python things
 | 
				
			||||||
 | 
					    W503
 | 
				
			||||||
exclude =
 | 
					exclude =
 | 
				
			||||||
    .git,
 | 
					    .git,
 | 
				
			||||||
    __pycache__,
 | 
					    __pycache__,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										6
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						| 
						 | 
					@ -6,8 +6,6 @@ flask.cfg
 | 
				
			||||||
/config.py
 | 
					/config.py
 | 
				
			||||||
redis_map.py
 | 
					redis_map.py
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Dev only files
 | 
					 | 
				
			||||||
test/
 | 
					 | 
				
			||||||
.idea
 | 
					.idea
 | 
				
			||||||
dev_*
 | 
					dev_*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,7 +13,7 @@ dev_*
 | 
				
			||||||
client/dev
 | 
					client/dev
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Dev file server
 | 
					# Dev file server
 | 
				
			||||||
storage/
 | 
					/storage/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Javascript packages
 | 
					# Javascript packages
 | 
				
			||||||
node_modules
 | 
					node_modules
 | 
				
			||||||
| 
						 | 
					@ -164,3 +162,5 @@ dmypy.json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Cython debug symbols
 | 
					# Cython debug symbols
 | 
				
			||||||
cython_debug/
 | 
					cython_debug/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.history
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,16 +0,0 @@
 | 
				
			||||||
repos:
 | 
					 | 
				
			||||||
  - repo: https://github.com/pre-commit/pre-commit-hooks
 | 
					 | 
				
			||||||
    rev: 38b88246ccc552bffaaf54259d064beeee434539 # frozen: v4.0.1
 | 
					 | 
				
			||||||
    hooks:
 | 
					 | 
				
			||||||
      - id: trailing-whitespace
 | 
					 | 
				
			||||||
      - id: end-of-file-fixer
 | 
					 | 
				
			||||||
      - id: check-yaml
 | 
					 | 
				
			||||||
      - id: check-added-large-files
 | 
					 | 
				
			||||||
  - repo: https://github.com/pycqa/flake8
 | 
					 | 
				
			||||||
    rev: "cbeb4c9c4137cff1568659fcc48e8b85cddd0c8d" # frozen: 4.0.1
 | 
					 | 
				
			||||||
    hooks:
 | 
					 | 
				
			||||||
      - id: flake8
 | 
					 | 
				
			||||||
  - repo: https://github.com/pre-commit/mirrors-autopep8
 | 
					 | 
				
			||||||
    rev: "7d14f78422aef2153a90e33373d2515bcc99038d" # frozen: v1.5.7
 | 
					 | 
				
			||||||
    hooks:
 | 
					 | 
				
			||||||
      - id: autopep8
 | 
					 | 
				
			||||||
							
								
								
									
										17
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						| 
						 | 
					@ -1,25 +1,18 @@
 | 
				
			||||||
FROM nikolaik/python-nodejs:python3.12-nodejs18
 | 
					FROM python:3.12
 | 
				
			||||||
 | 
					 | 
				
			||||||
RUN apt-get update && apt-get install -y libpq-dev curl jq
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
RUN curl -s -L $(curl https://api.github.com/repos/tus/tusd/releases/latest -s | jq '.assets[] | select(.name=="tusd_linux_amd64.tar.gz") | .browser_download_url' -r) | tar -xzvf - -C /usr/local/bin/ --strip-components=1 tusd_linux_amd64/tusd && chmod +x /usr/local/bin/tusd
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
WORKDIR /app
 | 
					WORKDIR /app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RUN apt-get update && apt-get install -y libpq-dev curl jq
 | 
				
			||||||
 | 
					
 | 
				
			||||||
COPY requirements.txt requirements.txt
 | 
					COPY requirements.txt requirements.txt
 | 
				
			||||||
RUN pip3 install -r requirements.txt
 | 
					RUN pip3 install -r requirements.txt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RUN npm install -g npm
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
RUN mkdir client
 | 
					 | 
				
			||||||
COPY ./client /app/client
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
RUN cd client && npm ci --also=dev && cd ..
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
COPY . /app
 | 
					COPY . /app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ENV LANG=C.UTF-8
 | 
					ENV LANG=C.UTF-8
 | 
				
			||||||
ARG GIT_COMMIT_HASH
 | 
					ARG GIT_COMMIT_HASH
 | 
				
			||||||
ENV GIT_COMMIT_HASH=${GIT_COMMIT_HASH:-undefined}
 | 
					ENV GIT_COMMIT_HASH=${GIT_COMMIT_HASH:-undefined}
 | 
				
			||||||
 | 
					ARG BUILD_DATE
 | 
				
			||||||
 | 
					ENV BUILD_DATE=${BUILD_DATE:-undefined}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CMD python -m src daemon
 | 
					CMD python -m src daemon
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										42
									
								
								Dockerfile-ci
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,42 @@
 | 
				
			||||||
 | 
					# Client build
 | 
				
			||||||
 | 
					FROM node:22.13 AS client-builder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					WORKDIR /app/client
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# kind of retarded but such is a price for inlining dependencies
 | 
				
			||||||
 | 
					COPY client/fluid-player/package.json client/fluid-player/package-lock.json ./fluid-player/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					COPY client/package.json client/package-lock.json ./
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RUN npm ci --include=dev
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					COPY schema ../schema
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					COPY config.json ../config.json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					COPY client ./
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RUN npm run build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Server build
 | 
				
			||||||
 | 
					FROM python:3.12 AS server-builder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RUN apt-get update \
 | 
				
			||||||
 | 
					  && apt-get install -y libpq-dev
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					WORKDIR /app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					COPY requirements.txt requirements.txt
 | 
				
			||||||
 | 
					RUN pip3 install -r requirements.txt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					COPY . /app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					COPY --from=client-builder /app/client/dist ./client/dist
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ENV LANG=C.UTF-8
 | 
				
			||||||
 | 
					ARG GIT_COMMIT_HASH
 | 
				
			||||||
 | 
					ENV GIT_COMMIT_HASH=${GIT_COMMIT_HASH:-undefined}
 | 
				
			||||||
 | 
					ARG BUILD_DATE
 | 
				
			||||||
 | 
					ENV BUILD_DATE=${BUILD_DATE:-undefined}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CMD python -m src daemon
 | 
				
			||||||
							
								
								
									
										13
									
								
								Dockerfile-client
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,13 @@
 | 
				
			||||||
 | 
					FROM node:22.13
 | 
				
			||||||
 | 
					WORKDIR /app/client
 | 
				
			||||||
 | 
					CMD [ "npm", "run", "build" ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RUN npm install -g npm
 | 
				
			||||||
 | 
					COPY ./ /app
 | 
				
			||||||
 | 
					RUN npm ci --include=dev
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ENV LANG=C.UTF-8
 | 
				
			||||||
 | 
					ARG GIT_COMMIT_HASH
 | 
				
			||||||
 | 
					ENV GIT_COMMIT_HASH=${GIT_COMMIT_HASH:-undefined}
 | 
				
			||||||
 | 
					ARG BUILD_DATE
 | 
				
			||||||
 | 
					ENV BUILD_DATE=${BUILD_DATE:-undefined}
 | 
				
			||||||
| 
						 | 
					@ -1,29 +0,0 @@
 | 
				
			||||||
# webpack output
 | 
					 | 
				
			||||||
**/dev
 | 
					 | 
				
			||||||
**/dist
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**/.classpath
 | 
					 | 
				
			||||||
**/.dockerignore
 | 
					 | 
				
			||||||
**/.env
 | 
					 | 
				
			||||||
**/.git
 | 
					 | 
				
			||||||
**/.gitignore
 | 
					 | 
				
			||||||
**/.project
 | 
					 | 
				
			||||||
**/.settings
 | 
					 | 
				
			||||||
**/.toolstarget
 | 
					 | 
				
			||||||
**/.vs
 | 
					 | 
				
			||||||
**/.vscode
 | 
					 | 
				
			||||||
**/*.code-workspace
 | 
					 | 
				
			||||||
**/*.*proj.user
 | 
					 | 
				
			||||||
**/*.dbmdl
 | 
					 | 
				
			||||||
**/*.jfm
 | 
					 | 
				
			||||||
**/azds.yaml
 | 
					 | 
				
			||||||
**/charts
 | 
					 | 
				
			||||||
**/docker-compose*
 | 
					 | 
				
			||||||
**/compose*
 | 
					 | 
				
			||||||
**/Dockerfile*
 | 
					 | 
				
			||||||
**/node_modules
 | 
					 | 
				
			||||||
**/npm-debug.log
 | 
					 | 
				
			||||||
**/obj
 | 
					 | 
				
			||||||
**/secrets.dev.yaml
 | 
					 | 
				
			||||||
**/values.dev.yaml
 | 
					 | 
				
			||||||
README.md
 | 
					 | 
				
			||||||
							
								
								
									
										3
									
								
								client/.vscode/extensions.json
									
									
									
									
										vendored
									
									
								
							
							
						
						| 
						 | 
					@ -1,3 +0,0 @@
 | 
				
			||||||
{
 | 
					 | 
				
			||||||
  "recommendations": []
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										12
									
								
								client/.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							
							
						
						| 
						 | 
					@ -1,14 +1,9 @@
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
					  "typescript.tsdk": "./node_modules/typescript/lib",
 | 
				
			||||||
 | 
					  "typescript.enablePromptUseWorkspaceTsdk": true,
 | 
				
			||||||
  "files.exclude": {
 | 
					  "files.exclude": {
 | 
				
			||||||
    "node_modules": true
 | 
					    "node_modules": true
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  // this option does work and is required for emmet in jinja to work
 | 
					 | 
				
			||||||
  "files.associations": {
 | 
					 | 
				
			||||||
    "*.html": "jinja-html"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "emmet.includeLanguages": {
 | 
					 | 
				
			||||||
    "jinja-html": "html"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "search.exclude": {
 | 
					  "search.exclude": {
 | 
				
			||||||
    "**/node_modules": true,
 | 
					    "**/node_modules": true,
 | 
				
			||||||
    "**/bower_components": true,
 | 
					    "**/bower_components": true,
 | 
				
			||||||
| 
						 | 
					@ -19,9 +14,6 @@
 | 
				
			||||||
  "javascript.preferences.importModuleSpecifierEnding": "js",
 | 
					  "javascript.preferences.importModuleSpecifierEnding": "js",
 | 
				
			||||||
  "javascript.preferences.quoteStyle": "double",
 | 
					  "javascript.preferences.quoteStyle": "double",
 | 
				
			||||||
  "javascript.format.semicolons": "insert",
 | 
					  "javascript.format.semicolons": "insert",
 | 
				
			||||||
  "[jinja-html]": {
 | 
					 | 
				
			||||||
    "editor.tabSize": 2
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "[javascript]": {
 | 
					  "[javascript]": {
 | 
				
			||||||
    "editor.tabSize": 2
 | 
					    "editor.tabSize": 2
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,16 +0,0 @@
 | 
				
			||||||
# syntax=docker/dockerfile:1
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
FROM node:16.14
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
ENV NODE_ENV=production
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
WORKDIR /app
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
COPY ["package.json", "package-lock.json", "/app/"]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
RUN npm install -g npm
 | 
					 | 
				
			||||||
RUN npm ci --also=dev
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
COPY . /app
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
CMD [ "npm", "run", "build" ]
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,15 +0,0 @@
 | 
				
			||||||
# syntax=docker/dockerfile:1
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
FROM node:12.22
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
ENV NODE_ENV=development
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
WORKDIR /app
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
COPY ["package.json", "package-lock.json*", "./"]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
RUN npm install
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
COPY . .
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
CMD ["npm", "run", "dev"]
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,92 +0,0 @@
 | 
				
			||||||
const path = require("path");
 | 
					 | 
				
			||||||
const fse = require("fs-extra");
 | 
					 | 
				
			||||||
const HTMLWebpackPlugin = require("html-webpack-plugin");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * @typedef BuildOptions
 | 
					 | 
				
			||||||
 * @property {string} fileExtension
 | 
					 | 
				
			||||||
 * @property {string} outputPrefix
 | 
					 | 
				
			||||||
 * @property {HTMLWebpackPlugin.Options} pluginOptions Webpack plugin options.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/** */
 | 
					 | 
				
			||||||
class TemplateFile {
 | 
					 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
   * @param {fse.Dirent} dirent
 | 
					 | 
				
			||||||
   * @param {string} path Absolute path to the file.
 | 
					 | 
				
			||||||
   */
 | 
					 | 
				
			||||||
  constructor(dirent, path) {
 | 
					 | 
				
			||||||
    this.dirent = dirent;
 | 
					 | 
				
			||||||
    this.path = path;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Builds an array of HTML webpack plugins from the provided folder.
 | 
					 | 
				
			||||||
 * @param {string} basePath Absolute path to the template folder.
 | 
					 | 
				
			||||||
 * @param {BuildOptions} options Build optons.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
function buildHTMLWebpackPluginsRecursive(basePath, options) {
 | 
					 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
   * @type {HTMLWebpackPlugin[]}
 | 
					 | 
				
			||||||
   */
 | 
					 | 
				
			||||||
  const plugins = [];
 | 
					 | 
				
			||||||
  const files = walkFolder(basePath);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  files.forEach((file) => {
 | 
					 | 
				
			||||||
    const isTemplateFile = file.dirent.isFile() && file.path.endsWith(`${options.fileExtension}`);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (isTemplateFile) {
 | 
					 | 
				
			||||||
      const outputBase = path.relative(basePath, file.path);
 | 
					 | 
				
			||||||
      const outputPath = path.join(path.basename(basePath), outputBase);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const webpackPlugin = new HTMLWebpackPlugin({
 | 
					 | 
				
			||||||
        ...options.pluginOptions,
 | 
					 | 
				
			||||||
        template: file.path,
 | 
					 | 
				
			||||||
        filename: outputPath,
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      plugins.push(webpackPlugin);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return plugins;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * @param {string} folderPath Absolute path to the folder.
 | 
					 | 
				
			||||||
 * @param {TemplateFile[]} files
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
function walkFolder(folderPath, files = [], currentCount = 0) {
 | 
					 | 
				
			||||||
  const nestedLimit = 1000;
 | 
					 | 
				
			||||||
  const folderContents = fse.readdirSync(folderPath, { withFileTypes: true });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  folderContents.forEach((entry) => {
 | 
					 | 
				
			||||||
    const file = entry.isFile() && entry;
 | 
					 | 
				
			||||||
    const folder = entry.isDirectory() && entry;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (file) {
 | 
					 | 
				
			||||||
      const filePath = path.join(folderPath, file.name);
 | 
					 | 
				
			||||||
      files.push(new TemplateFile(file, filePath));
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (folder) {
 | 
					 | 
				
			||||||
      currentCount++;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (currentCount > nestedLimit) {
 | 
					 | 
				
			||||||
        throw new Error(`The folder at "${folderPath}" contains more than ${nestedLimit} folders.`);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const newFolderPath = path.join(folderPath, folder.name);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      return walkFolder(newFolderPath, files, currentCount);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return files;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
module.exports = {
 | 
					 | 
				
			||||||
  buildHTMLWebpackPluginsRecursive,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,5 +0,0 @@
 | 
				
			||||||
{
 | 
					 | 
				
			||||||
  "html": {
 | 
					 | 
				
			||||||
    "snippets": {}
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										42
									
								
								client/configs/parse-config.mjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,42 @@
 | 
				
			||||||
 | 
					// @ts-check
 | 
				
			||||||
 | 
					import path from "node:path";
 | 
				
			||||||
 | 
					import fs from "node:fs";
 | 
				
			||||||
 | 
					import Ajv  from "ajv";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ajv = new Ajv();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @returns {import("../../schema/config.ts").IConfiguration}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function parseConfiguration() {
 | 
				
			||||||
 | 
					  const configSchemaPath = path.resolve(
 | 
				
			||||||
 | 
					    __dirname,
 | 
				
			||||||
 | 
					    "..",
 | 
				
			||||||
 | 
					    "..",
 | 
				
			||||||
 | 
					    "schema",
 | 
				
			||||||
 | 
					    "config.schema.json"
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  const configPath = path.resolve(__dirname, "..", "..", "config.json");
 | 
				
			||||||
 | 
					  const configFileSchemaContent = fs.readFileSync(configSchemaPath, {
 | 
				
			||||||
 | 
					    encoding: "utf8",
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  const configFileContent = fs.readFileSync(configPath, { encoding: "utf8" });
 | 
				
			||||||
 | 
					  const configSchema = JSON.parse(configFileSchemaContent);
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * @type {import("../../schema/config.ts").IConfiguration}
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  const config = JSON.parse(configFileContent);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const isValid = ajv.validate(configSchema, config);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!isValid) {
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @type {import("ajv").ErrorObject<string, Record<string, any>, unknown>[]}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    // @ts-expect-error
 | 
				
			||||||
 | 
					    const errors = ajv.errors
 | 
				
			||||||
 | 
					    throw new AggregateError(errors, "Failed to validate the config.")
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return config;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,21 +0,0 @@
 | 
				
			||||||
const path = require("path");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
require("dotenv").config({
 | 
					 | 
				
			||||||
  path: path.resolve(__dirname, "..", ".."),
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const kemonoSite = process.env.KEMONO_SITE || "http://localhost:5000";
 | 
					 | 
				
			||||||
const nodeEnv = process.env.NODE_ENV || "production";
 | 
					 | 
				
			||||||
const iconsPrepend = process.env.ICONS_PREPEND || "";
 | 
					 | 
				
			||||||
const bannersPrepend = process.env.BANNERS_PREPEND || "";
 | 
					 | 
				
			||||||
const thumbnailsPrepend = process.env.THUMBNAILS_PREPEND || "";
 | 
					 | 
				
			||||||
const creatorsLocation = process.env.CREATORS_LOCATION || "";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
module.exports = {
 | 
					 | 
				
			||||||
  kemonoSite,
 | 
					 | 
				
			||||||
  nodeEnv,
 | 
					 | 
				
			||||||
  iconsPrepend,
 | 
					 | 
				
			||||||
  bannersPrepend,
 | 
					 | 
				
			||||||
  thumbnailsPrepend,
 | 
					 | 
				
			||||||
  creatorsLocation,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
							
								
								
									
										49
									
								
								client/configs/vars.mjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,49 @@
 | 
				
			||||||
 | 
					// @ts-check
 | 
				
			||||||
 | 
					import { parseConfiguration } from "./parse-config.mjs";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const configuration = parseConfiguration();
 | 
				
			||||||
 | 
					export const { webserver } = configuration;
 | 
				
			||||||
 | 
					export const apiServerBaseURL = configuration.webserver.base_url;
 | 
				
			||||||
 | 
					export const sentryDSN = configuration.sentry_dsn_js;
 | 
				
			||||||
 | 
					export const apiServerPort = !apiServerBaseURL
 | 
				
			||||||
 | 
					  ? undefined
 | 
				
			||||||
 | 
					  : configuration.webserver?.port;
 | 
				
			||||||
 | 
					export const siteName = configuration.webserver.ui.home.site_name || "Kemono";
 | 
				
			||||||
 | 
					export const favicon = configuration.webserver.ui.favicon || "./static/favicon.ico";
 | 
				
			||||||
 | 
					export const homeBackgroundImage =
 | 
				
			||||||
 | 
					  configuration.webserver.ui.home.home_background_image;
 | 
				
			||||||
 | 
					export const homeMascotPath = configuration.webserver.ui.home.mascot_path;
 | 
				
			||||||
 | 
					export const homeLogoPath = configuration.webserver.ui.home.logo_path;
 | 
				
			||||||
 | 
					export const homeWelcomeCredits = configuration.webserver.ui.home.welcome_credits;
 | 
				
			||||||
 | 
					export const homeAnnouncements = configuration.webserver.ui.home.announcements;
 | 
				
			||||||
 | 
					// TODO: in development it should point to webpack server
 | 
				
			||||||
 | 
					export const kemonoSite = configuration.site || "http://localhost:5000";
 | 
				
			||||||
 | 
					export const paysiteList = configuration.webserver.ui.config.paysite_list;
 | 
				
			||||||
 | 
					export const artistsOrCreators =
 | 
				
			||||||
 | 
					  configuration.webserver.ui.config.artists_or_creators ?? "Artists";
 | 
				
			||||||
 | 
					export const disableDMs = configuration.webserver.ui.sidebar?.disable_dms ?? true;
 | 
				
			||||||
 | 
					export const disableFAQ = configuration.webserver.ui.sidebar?.disable_faq ?? true;
 | 
				
			||||||
 | 
					export const disableFilehaus =
 | 
				
			||||||
 | 
					  configuration.webserver.ui.sidebar?.disable_filehaus ?? true;
 | 
				
			||||||
 | 
					export const sidebarItems = configuration.webserver.ui.sidebar_items;
 | 
				
			||||||
 | 
					export const footerItems = configuration.webserver.ui.footer_items;
 | 
				
			||||||
 | 
					export const bannerGlobal = configuration.webserver.ui.banner?.global;
 | 
				
			||||||
 | 
					export const AnnouncementBannerGlobal = configuration.webserver.ui.banner?.announcement_global;
 | 
				
			||||||
 | 
					export const bannerWelcome = configuration.webserver.ui.banner?.welcome;
 | 
				
			||||||
 | 
					export const headerAd = configuration.webserver.ui.ads?.header;
 | 
				
			||||||
 | 
					export const middleAd = configuration.webserver.ui.ads?.middle;
 | 
				
			||||||
 | 
					export const footerAd = configuration.webserver.ui.ads?.footer;
 | 
				
			||||||
 | 
					export const sliderAd = configuration.webserver.ui.ads?.slider;
 | 
				
			||||||
 | 
					export const videoAd = configuration.webserver.ui.ads?.video;
 | 
				
			||||||
 | 
					export const isArchiveServerEnabled = configuration.archive_server?.enabled ?? false;
 | 
				
			||||||
 | 
					export const analyticsEnabled = configuration.webserver.ui.matomo?.enabled ?? false;
 | 
				
			||||||
 | 
					export const analyticsCode = configuration.webserver.ui.matomo?.plain_code;
 | 
				
			||||||
 | 
					export const iconsPrepend = webserver.ui.files_url_prepend?.icons_base_url || "";
 | 
				
			||||||
 | 
					export const bannersPrepend = webserver.ui.files_url_prepend?.banners_base_url || "";
 | 
				
			||||||
 | 
					export const thumbnailsPrepend =
 | 
				
			||||||
 | 
					  webserver.ui.files_url_prepend?.thumbnails_base_url || "";
 | 
				
			||||||
 | 
					export const isFileServingEnabled = Boolean(
 | 
				
			||||||
 | 
					  configuration.archive_server?.file_serving_enabled
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					export const gitCommitHash = process.env.GIT_COMMIT_HASH;
 | 
				
			||||||
 | 
					export const buildDate = process.env.BUILD_DATE;
 | 
				
			||||||
							
								
								
									
										10
									
								
								client/extra.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,10 @@
 | 
				
			||||||
 | 
					// required for typescript not to choke on css modules
 | 
				
			||||||
 | 
					declare module '*.scss' {
 | 
				
			||||||
 | 
					  const classes: { [key: string]: string };
 | 
				
			||||||
 | 
					  export = classes;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					declare module '*.yaml' {
 | 
				
			||||||
 | 
					  const data: any
 | 
				
			||||||
 | 
					  export default data
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										3
									
								
								client/fluid-player/.babelrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,3 @@
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "presets": ["@babel/preset-env"]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										15
									
								
								client/fluid-player/.editorconfig
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,15 @@
 | 
				
			||||||
 | 
					root = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[*]
 | 
				
			||||||
 | 
					charset = utf-8
 | 
				
			||||||
 | 
					indent_style = space
 | 
				
			||||||
 | 
					indent_size = 4
 | 
				
			||||||
 | 
					end_of_line = lf
 | 
				
			||||||
 | 
					insert_final_newline = true
 | 
				
			||||||
 | 
					trim_trailing_whitespace = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[package.json]
 | 
				
			||||||
 | 
					indent_size = 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[*.{js, css}]
 | 
				
			||||||
 | 
					indent_size = 4
 | 
				
			||||||
							
								
								
									
										41
									
								
								client/fluid-player/.github/ISSUE_TEMPLATE/bug_report.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,41 @@
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					name: Bug report
 | 
				
			||||||
 | 
					about: Create a report to help us improve
 | 
				
			||||||
 | 
					title: ''
 | 
				
			||||||
 | 
					labels: bug, needs triage
 | 
				
			||||||
 | 
					assignees: ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Describe the bug**
 | 
				
			||||||
 | 
					A clear and concise description of what the bug is.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**To Reproduce**
 | 
				
			||||||
 | 
					Steps to reproduce the behavior:
 | 
				
			||||||
 | 
					1. Go to '...'
 | 
				
			||||||
 | 
					2. Click on '....'
 | 
				
			||||||
 | 
					3. Scroll down to '....'
 | 
				
			||||||
 | 
					4. See error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Expected behavior**
 | 
				
			||||||
 | 
					A clear and concise description of what you expected to happen.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Screenshots or Links**
 | 
				
			||||||
 | 
					If applicable, add screenshots or links to help explain your problem. **DO NOT** include links to NSFW webpages. Preferably create code to reproduce using https://jsfiddle.net
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Desktop (please complete the following information if relevant):**
 | 
				
			||||||
 | 
					 - OS: [e.g. iOS]
 | 
				
			||||||
 | 
					 - Browser [e.g. chrome, safari]
 | 
				
			||||||
 | 
					 - Version [e.g. 22]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Smartphone (please complete the following information if relevant):**
 | 
				
			||||||
 | 
					 - Device: [e.g. iPhone6]
 | 
				
			||||||
 | 
					 - OS: [e.g. iOS8.1]
 | 
				
			||||||
 | 
					 - Browser [e.g. stock browser, safari]
 | 
				
			||||||
 | 
					 - Version [e.g. 22]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Affected version**
 | 
				
			||||||
 | 
					For example, v3.0.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Additional context**
 | 
				
			||||||
 | 
					Add any other context about the problem here.
 | 
				
			||||||
							
								
								
									
										20
									
								
								client/fluid-player/.github/ISSUE_TEMPLATE/feature_request.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,20 @@
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					name: Feature request
 | 
				
			||||||
 | 
					about: Suggest an idea for this project
 | 
				
			||||||
 | 
					title: ''
 | 
				
			||||||
 | 
					labels: feature request, needs triage
 | 
				
			||||||
 | 
					assignees: ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Is your feature request related to a problem? Please describe.**
 | 
				
			||||||
 | 
					A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Describe the solution you'd like**
 | 
				
			||||||
 | 
					A clear and concise description of what you want to happen.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Describe alternatives you've considered**
 | 
				
			||||||
 | 
					A clear and concise description of any alternative solutions or features you've considered.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Additional context**
 | 
				
			||||||
 | 
					Add any other context or screenshots about the feature request here.
 | 
				
			||||||
							
								
								
									
										13
									
								
								client/fluid-player/.github/ISSUE_TEMPLATE/question.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,13 @@
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					name: Question
 | 
				
			||||||
 | 
					about: Question or clarification about Fluid Player or related technologies
 | 
				
			||||||
 | 
					title: ''
 | 
				
			||||||
 | 
					labels: question, needs triage
 | 
				
			||||||
 | 
					assignees: ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					A clear and concise description of what the problem is.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Additional context**
 | 
				
			||||||
 | 
					Add any other context or screenshots about the request here.
 | 
				
			||||||
							
								
								
									
										16
									
								
								client/fluid-player/.github/pull_request_template.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,16 @@
 | 
				
			||||||
 | 
					**IMPORTANT: Please do not create a Pull Request without creating an issue first.**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*Any change needs to be discussed before proceeding. Failure to do so may result in the rejection of the pull request.*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Proposed Changes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. ...
 | 
				
			||||||
 | 
					2. ...
 | 
				
			||||||
 | 
					3. ...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Relevant issues
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Closes #...
 | 
				
			||||||
 | 
					Closes #...
 | 
				
			||||||
 | 
					Closes #...
 | 
				
			||||||
 | 
					Related #...
 | 
				
			||||||
							
								
								
									
										25
									
								
								client/fluid-player/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,25 @@
 | 
				
			||||||
 | 
					node_modules/
 | 
				
			||||||
 | 
					dist/
 | 
				
			||||||
 | 
					dist-cdn/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# IDEs and editors
 | 
				
			||||||
 | 
					/.idea
 | 
				
			||||||
 | 
					.project
 | 
				
			||||||
 | 
					.classpath
 | 
				
			||||||
 | 
					.c9/
 | 
				
			||||||
 | 
					*.launch
 | 
				
			||||||
 | 
					.settings/
 | 
				
			||||||
 | 
					*.sublime-workspace
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# IDE - VSCode
 | 
				
			||||||
 | 
					.vscode/*
 | 
				
			||||||
 | 
					.vscode/settings.json
 | 
				
			||||||
 | 
					!.vscode/tasks.json
 | 
				
			||||||
 | 
					!.vscode/launch.json
 | 
				
			||||||
 | 
					!.vscode/extensions.json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# E2E
 | 
				
			||||||
 | 
					/test-results/
 | 
				
			||||||
 | 
					/playwright-report/
 | 
				
			||||||
 | 
					/blob-report/
 | 
				
			||||||
 | 
					/playwright/.cache/
 | 
				
			||||||
							
								
								
									
										9
									
								
								client/fluid-player/.npmignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,9 @@
 | 
				
			||||||
 | 
					test
 | 
				
			||||||
 | 
					.idea
 | 
				
			||||||
 | 
					e2e
 | 
				
			||||||
 | 
					.editorconfig
 | 
				
			||||||
 | 
					yarn.lock
 | 
				
			||||||
 | 
					webpack.config.js
 | 
				
			||||||
 | 
					dist
 | 
				
			||||||
 | 
					dist-cdn
 | 
				
			||||||
 | 
					.github
 | 
				
			||||||
							
								
								
									
										372
									
								
								client/fluid-player/CHANGELOG.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,372 @@
 | 
				
			||||||
 | 
					# CHANGELOG
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.46.0 (2024-12-12)
 | 
				
			||||||
 | 
					* [Pull #854](https://github.com/fluid-player/fluid-player/pull/854) Add support for automatic landscape screen orientation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.45.0 (2024-12-09)
 | 
				
			||||||
 | 
					* [Pull #859](https://github.com/fluid-player/fluid-player/pull/859) Create E2E project for Fluid Player
 | 
				
			||||||
 | 
					* [Pull #857](https://github.com/fluid-player/fluid-player/pull/857) Live Indicator
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.44.0 (2024-11-14)
 | 
				
			||||||
 | 
					* [Pull #851](https://github.com/fluid-player/fluid-player/pull/851) Mobile on click show control bar instead of pausing video
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.43.0 (2024-11-13)
 | 
				
			||||||
 | 
					* [Pull #855](https://github.com/fluid-player/fluid-player/pull/855) Fluidplayer doesn't play Wrapper ads
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.42.0 (2024-11-05)
 | 
				
			||||||
 | 
					* [Pull #836](https://github.com/fluid-player/fluid-player/pull/836) FallbackVastTags may not be working
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.41.0 (2024-11-04)
 | 
				
			||||||
 | 
					* [Pull #850](https://github.com/fluid-player/fluid-player/pull/850) New event system breaks current implementations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.40.0 (2024-10-24)
 | 
				
			||||||
 | 
					* [Pull #844](https://github.com/fluid-player/fluid-player/pull/844) MiniPlayer Follow Up > Improving UX and be Google compliant
 | 
				
			||||||
 | 
					* [Pull #846](https://github.com/fluid-player/fluid-player/pull/846) Rounded corners on player
 | 
				
			||||||
 | 
					* [Pull #848](https://github.com/fluid-player/fluid-player/pull/848) Uncaught TypeError: Cannot read properties of undefined (reading 'Parser') when using subtitles
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.39.0 (2024-10-15)
 | 
				
			||||||
 | 
					* [Pull #842](https://github.com/fluid-player/fluid-player/pull/842) Ads events
 | 
				
			||||||
 | 
					* [Pull #843](https://github.com/fluid-player/fluid-player/pull/843) Mid Roll is not working with Live stream videos
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.38.0 (2024-10-02)
 | 
				
			||||||
 | 
					* [Pull #838](https://github.com/fluid-player/fluid-player/pull/838) TheatreAdvanced is not working and breaks player
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.37.0 (2024-09-17)
 | 
				
			||||||
 | 
					* [Pull #821](https://github.com/fluid-player/fluid-player/pull/821) Added support for VAST ViewableImpression
 | 
				
			||||||
 | 
					* [Pull #833](https://github.com/fluid-player/fluid-player/pull/833) doubleclickFullscreen not working
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.36.0 (2024-09-05)
 | 
				
			||||||
 | 
					* [Pull #832](https://github.com/fluid-player/fluid-player/pull/832) Show quality change icon in player when working with hls (m3u8)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.35.0 (2024-08-01)
 | 
				
			||||||
 | 
					* [Pull #825](https://github.com/fluid-player/fluid-player/pull/825) Fix image freezing
 | 
				
			||||||
 | 
					* [Pull #826](https://github.com/fluid-player/fluid-player/pull/826) Ads cannot be displayed when XML content has two Creative tags
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.34.0 (2024-07-11)
 | 
				
			||||||
 | 
					* [Pull #819](https://github.com/fluid-player/fluid-player/pull/819) When users don't have suggested videos, there's a console error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.33.0 (2024-07-02)
 | 
				
			||||||
 | 
					* [Pull #818](https://github.com/fluid-player/fluid-player/pull/818) Suggested Videos Feature in Fluid Player
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.32.0 (2024-03-21)
 | 
				
			||||||
 | 
					* [Pull #804](https://github.com/fluid-player/fluid-player/pull/804) htmlOnPauseBlock not being displayed with last version of FP
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.31.0 (2024-01-25)
 | 
				
			||||||
 | 
					* [Pull #788](https://github.com/fluid-player/fluid-player/pull/788) Fluid Player doesn't work with Next.js
 | 
				
			||||||
 | 
					* [Pull #791](https://github.com/fluid-player/fluid-player/pull/791) Impression events aren't called for some VAST tags within the VAST wrapper chain
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.30.0 (2024-01-11)
 | 
				
			||||||
 | 
					* [Pull #789](https://github.com/fluid-player/fluid-player/pull/789) Uncaught TypeError: Cannot set properties of null (setting 'slotIframe') while testing on VOD with VPAID Non Linear
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.29.0 (2023-12-14)
 | 
				
			||||||
 | 
					* [Pull #783](https://github.com/fluid-player/fluid-player/pull/783) Fix test cases in the development sever
 | 
				
			||||||
 | 
					* [Pull #785](https://github.com/fluid-player/fluid-player/pull/785) FluidPlayer > Error > onPauseRoll In Video Banner VAST
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.28.0 (2023-11-16)
 | 
				
			||||||
 | 
					* [Pull #776](https://github.com/fluid-player/fluid-player/pull/776) Fluid Player doesn't work with shadow DOM
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.27.0 (2023-10-19)
 | 
				
			||||||
 | 
					* [Pull #771](https://github.com/fluid-player/fluid-player/pull/771) Method loadVpaid create frame without body
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.26.0 (2023-10-12)
 | 
				
			||||||
 | 
					* [Pull #768](https://github.com/fluid-player/fluid-player/pull/768) Stretch posterImage to playerwindow
 | 
				
			||||||
 | 
					* [Pull #769](https://github.com/fluid-player/fluid-player/pull/769) Events don't differ from Ad and Main video
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.25.0 (2023-09-21)
 | 
				
			||||||
 | 
					* [Pull #766](https://github.com/fluid-player/fluid-player/pull/766) adClickable and CTA parameters don't work when 2 VAST ad is loaded
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.24.0 (2023-09-08)
 | 
				
			||||||
 | 
					* [Pull #763](https://github.com/fluid-player/fluid-player/pull/763) Player can't be restarted by destroying it and initializing it again
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.23.0 (2023-08-28)
 | 
				
			||||||
 | 
					* [Pull #757](https://github.com/fluid-player/fluid-player/pull/757) Preload doesn't work for .m3u8 files when the player is serving in-stream ads
 | 
				
			||||||
 | 
					* [Pull #760](https://github.com/fluid-player/fluid-player/pull/760) Lighthouse says: Does not use passive listeners to improve scrolling performance
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.22.0 (2023-08-17)
 | 
				
			||||||
 | 
					* [Pull #755](https://github.com/fluid-player/fluid-player/pull/755) Percentages are not accepted for "timer" property
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.21.0 (2023-08-08)
 | 
				
			||||||
 | 
					* [Pull #748](https://github.com/fluid-player/fluid-player/pull/748) CurrentTime reset after switch HLS source on IOS
 | 
				
			||||||
 | 
					* [Pull #752](https://github.com/fluid-player/fluid-player/pull/752) Add "controlForwardBackward" setting inside the player instead of the control bar
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.20.0 (2023-07-24)
 | 
				
			||||||
 | 
					* [Pull #749](https://github.com/fluid-player/fluid-player/pull/749) Mouse disappears
 | 
				
			||||||
 | 
					* [Pull #750](https://github.com/fluid-player/fluid-player/pull/750) In mobile and when using .m3u8 files, user needs to click the player twice in order to play the video
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.19.0 (2023-07-12)
 | 
				
			||||||
 | 
					* [Pull #746](https://github.com/fluid-player/fluid-player/pull/746) Could not find a declaration file for module 'fluid-player'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.18.0 (2023-06-30)
 | 
				
			||||||
 | 
					* [Pull #744](https://github.com/fluid-player/fluid-player/pull/744) Failed to resolve import "cheerio/lib/api/traversing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.17.0 (2023-06-16)
 | 
				
			||||||
 | 
					* [Pull #734](https://github.com/fluid-player/fluid-player/pull/734) MiniPlayer Mobile support
 | 
				
			||||||
 | 
					* [Pull #735](https://github.com/fluid-player/fluid-player/pull/735) MiniPlayer position configuration
 | 
				
			||||||
 | 
					* [Pull #739](https://github.com/fluid-player/fluid-player/pull/739) MiniPlayer Activate miniplayer when scrolling goes out of viewport
 | 
				
			||||||
 | 
					* [Pull #740](https://github.com/fluid-player/fluid-player/pull/740) MiniPlayer Allowing trigger MiniPlayer by code
 | 
				
			||||||
 | 
					* [Pull #736](https://github.com/fluid-player/fluid-player/pull/736) Fluid player doesn't track clicks on some sites
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.16.0 (2023-05-31)
 | 
				
			||||||
 | 
					* [Pull #721](https://github.com/fluid-player/fluid-player/pull/721) Fluid Player loads midRoll even if timer is longer than main video
 | 
				
			||||||
 | 
					* [Pull #732](https://github.com/fluid-player/fluid-player/pull/732) MiniPlayer MVP
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.15.0 (2023-05-09)
 | 
				
			||||||
 | 
					* [Pull #727](https://github.com/fluid-player/fluid-player/pull/727) Player required 2 clicks to play video New Iphone 11 Pro Chrome
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.14.0 (2023-04-27)
 | 
				
			||||||
 | 
					* [Pull #722](https://github.com/fluid-player/fluid-player/pull/722) Support Video Waterfall VAST Response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.13.0 (2023-03-09)
 | 
				
			||||||
 | 
					* [Pull #714](https://github.com/fluid-player/fluid-player/pull/714) Support Video Waterfall VAST Response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.12.0 (2023-01-10)
 | 
				
			||||||
 | 
					* [Pull #707](https://github.com/fluid-player/fluid-player/pull/707) Decrease volumes & revenue on In-stream zones
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.11.1 (2023-01-03)
 | 
				
			||||||
 | 
					* Update dependencies
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.11.0 (2023-01-03)
 | 
				
			||||||
 | 
					* [Pull #614](https://github.com/fluid-player/fluid-player/pull/614) Add check for process being defined
 | 
				
			||||||
 | 
					* [Pull #561](https://github.com/fluid-player/fluid-player/pull/561) Allow customizing playbackRates
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.10.0 (2022-12-15)
 | 
				
			||||||
 | 
					* [Pull #687](https://github.com/fluid-player/fluid-player/pull/687) LocalStorage not available in Chrome incognito mode is breaking JS
 | 
				
			||||||
 | 
					* [Pull #704](https://github.com/fluid-player/fluid-player/pull/704) In ad serving, the skip button when is without time delay it should trigger immediately the "skip ad"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.9.0 (2022-10-18)
 | 
				
			||||||
 | 
					* [Pull #688](https://github.com/fluid-player/fluid-player/pull/688) Selecting subtitle by default
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.8.0 (2022-10-07)
 | 
				
			||||||
 | 
					* [Pull #685](https://github.com/fluid-player/fluid-player/pull/685) Fluidplayer > 'autoHide' in Desktop only works when the cursor is on top of the player.
 | 
				
			||||||
 | 
					* [Pull #689](https://github.com/fluid-player/fluid-player/pull/689) Mobile view_ Error in console and wrong positioning of the CTA text
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.7.0 (2022-09-28)
 | 
				
			||||||
 | 
					* [Pull #650](https://github.com/fluid-player/fluid-player/pull/650) Play main video after preRoll ends not work
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.6.0 (2022-08-24)
 | 
				
			||||||
 | 
					* [Pull #666](https://github.com/fluid-player/fluid-player/pull/666) CTA Overlay > cannot be removed by publisher
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.5.0 (2022-07-20)
 | 
				
			||||||
 | 
					* [Pull #669](https://github.com/fluid-player/fluid-player/pull/669) Video CTA / Fluid Player
 | 
				
			||||||
 | 
					* [Pull #671](https://github.com/fluid-player/fluid-player/pull/671) Video CTA - update FP to support new CTA structure from VAST Tag
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.4.0 (2022-07-05)
 | 
				
			||||||
 | 
					* [Pull #652](https://github.com/fluid-player/fluid-player/pull/652) Video on demand and linear VPAID does not work on iOS Safari
 | 
				
			||||||
 | 
					* [Pull #664](https://github.com/fluid-player/fluid-player/pull/664) Video CTA / Fluid Player
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.3.0 (2022-06-22)
 | 
				
			||||||
 | 
					* [Pull #656](https://github.com/fluid-player/fluid-player/pull/656) Video CTA / Fluid Player
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.2.1 (2022-05-17)
 | 
				
			||||||
 | 
					* Update dependencies
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.2.0
 | 
				
			||||||
 | 
					* [Pull #641](https://github.com/fluid-player/fluid-player/pull/641) FluidPlayer > Streaming files compatibility
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.1.0
 | 
				
			||||||
 | 
					* [Pull #619](https://github.com/fluid-player/fluid-player/pull/619) Looping videos show loading spinner for a split second before looping
 | 
				
			||||||
 | 
					* [Pull #621](https://github.com/fluid-player/fluid-player/pull/621) FluidPlayer> Mouse disappears
 | 
				
			||||||
 | 
					* [Pull #622](https://github.com/fluid-player/fluid-player/pull/622) contextMenu.links labels are not working after play
 | 
				
			||||||
 | 
					* [Pull #628](https://github.com/fluid-player/fluid-player/pull/628) Overlay html over video (also in fullscreen)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.0.4
 | 
				
			||||||
 | 
					* [Pull #489](https://github.com/fluid-player/fluid-player/pull/489) Fix issues with nonLinear ads not closing
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.0.3
 | 
				
			||||||
 | 
					* [Pull #478](https://github.com/fluid-player/fluid-player/pull/478) Ensure options is object, fix dash debug
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.0.2
 | 
				
			||||||
 | 
					* [Pull #473](https://github.com/fluid-player/fluid-player/pull/473) Responsive test case
 | 
				
			||||||
 | 
					* [Pull #474](https://github.com/fluid-player/fluid-player/pull/474) Fix issues related to missing ended event
 | 
				
			||||||
 | 
					* [Pull #472](https://github.com/fluid-player/fluid-player/pull/472) Small refactor, add auto-hide test case
 | 
				
			||||||
 | 
					* [Pull #471](https://github.com/fluid-player/fluid-player/pull/471) Move X seconds forward/back
 | 
				
			||||||
 | 
					* [Pull #470](https://github.com/fluid-player/fluid-player/pull/470) Custom context
 | 
				
			||||||
 | 
					* [Pull #469](https://github.com/fluid-player/fluid-player/pull/469) Prevent hidden menus after cssmin optimization
 | 
				
			||||||
 | 
					* [Pull #468](https://github.com/fluid-player/fluid-player/pull/468) Config callbacks
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.0.1
 | 
				
			||||||
 | 
					* [Pull #457](https://github.com/fluid-player/fluid-player/pull/430) Fix ad skip button not showing properly
 | 
				
			||||||
 | 
					* [Pull #450](https://github.com/fluid-player/fluid-player/pull/430) Static thumbnail configuration
 | 
				
			||||||
 | 
					* [Pull #458](https://github.com/fluid-player/fluid-player/pull/430) Fix dash.js initialization and swap Vtt.js to Videojs fork
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3.0.0
 | 
				
			||||||
 | 
					* [Pull #441](https://github.com/fluid-player/fluid-player/pull/441) Major release - see pull request for full changelist.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2.4.11
 | 
				
			||||||
 | 
					* [Pull #430](https://github.com/fluid-player/fluid-player/pull/430) Add destroy function
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2.4.10
 | 
				
			||||||
 | 
					* [Pull #399](https://github.com/fluid-player/fluid-player/pull/399) Adding VR Features to player (experimental)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2.4.9
 | 
				
			||||||
 | 
					* [Pull #398](https://github.com/fluid-player/fluid-player/pull/398) Add support for VPAID (2.0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2.4.8
 | 
				
			||||||
 | 
					* [Pull #374](https://github.com/fluid-player/fluid-player/pull/374) Skip ad button on VAST preroll opening a new blank tab
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2.4.7
 | 
				
			||||||
 | 
					* [Pull #361](https://github.com/fluid-player/fluid-player/pull/361) Adding subtitles, multiple ad-roll, fallback vast ad, fixing dash playback, double click to fullscreen
 | 
				
			||||||
 | 
					* [Pull #354](https://github.com/fluid-player/fluid-player/pull/354) VAST Multiple mediafile support, announce proper error codes and some bug fixes
 | 
				
			||||||
 | 
					* [Pull #356](https://github.com/fluid-player/fluid-player/pull/356) Seeked and ended html5 event listeners
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2.4.6
 | 
				
			||||||
 | 
					* [Pull #358](https://github.com/fluid-player/fluid-player/pull/358) fix bug with dash js api
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2.4.5
 | 
				
			||||||
 | 
					* [Pull #325](https://github.com/fluid-player/fluid-player/pull/325) Add poster image size option (posterImageSize)
 | 
				
			||||||
 | 
					* [Pull #330](https://github.com/fluid-player/fluid-player/pull/330) Add showPlayButton config to display Play button on ad
 | 
				
			||||||
 | 
					* [Pull #306](https://github.com/fluid-player/fluid-player/pull/306) Remove unsupported browser layout parts
 | 
				
			||||||
 | 
					* [Pull #331](https://github.com/fluid-player/fluid-player/pull/331) Add ability to change controls titles
 | 
				
			||||||
 | 
					* [Pull #332](https://github.com/fluid-player/fluid-player/pull/332) Fix multiple videos play
 | 
				
			||||||
 | 
					* [Pull #335](https://github.com/fluid-player/fluid-player/pull/335) Improve timecode
 | 
				
			||||||
 | 
					* [Pull #336](https://github.com/fluid-player/fluid-player/pull/336) Add title
 | 
				
			||||||
 | 
					* [Pull #334](https://github.com/fluid-player/fluid-player/pull/334) Add ability to set preload value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2.4.4
 | 
				
			||||||
 | 
					* [Pull #289](https://github.com/fluid-player/fluid-player/pull/289) Fix window.getComputedStyle call on null
 | 
				
			||||||
 | 
					* [Pull #290](https://github.com/fluid-player/fluid-player/pull/290) Prevent multi click event on download btn
 | 
				
			||||||
 | 
					* [Pull #293](https://github.com/fluid-player/fluid-player/pull/293) Check if Hls already exposed in window
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2.4.3
 | 
				
			||||||
 | 
					* [Pull #266](https://github.com/fluid-player/fluid-player/pull/266) Fix play pause issue on mobile
 | 
				
			||||||
 | 
					* [Pull #268](https://github.com/fluid-player/fluid-player/pull/268) Fix iOS scrubbing bugs
 | 
				
			||||||
 | 
					* [Pull #270](https://github.com/fluid-player/fluid-player/pull/270) Fix for iOS switching to unsupported file types
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2.4.2
 | 
				
			||||||
 | 
					* [Pull #235](https://github.com/fluid-player/fluid-player/pull/235) [Pull #236](https://github.com/fluid-player/fluid-player/pull/236) Fix the controls randomly disappearing on scrubbing clicks
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2.4.1
 | 
				
			||||||
 | 
					* [Pull #228](https://github.com/fluid-player/fluid-player/pull/228) Persistent volume settings from before mute on page navigation
 | 
				
			||||||
 | 
					* [Pull #229](https://github.com/fluid-player/fluid-player/pull/229) Link to FP on menu button working correctly
 | 
				
			||||||
 | 
					* [Pull #230](https://github.com/fluid-player/fluid-player/pull/230) Fix for right click on initial play button
 | 
				
			||||||
 | 
					* [Pull #227](https://github.com/fluid-player/fluid-player/pull/227) Optional parameter to disable clickthrough layer on instream ads
 | 
				
			||||||
 | 
					* [Pull #231](https://github.com/fluid-player/fluid-player/pull/231) Fixes for how thumbnails are drawn and mouse event detection
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2.4.0
 | 
				
			||||||
 | 
					* [Pull #214](https://github.com/fluid-player/fluid-player/pull/241) Avoid looping VAST Ad
 | 
				
			||||||
 | 
					* [Pull #206](https://github.com/fluid-player/fluid-player/pull/206) Fix tracking impression events for nonLinear ads
 | 
				
			||||||
 | 
					* [Pull #221](https://github.com/fluid-player/fluid-player/pull/221) Fix VAST loading issue by AdBlock
 | 
				
			||||||
 | 
					* [Pull #207](https://github.com/fluid-player/fluid-player/pull/207) Add support for HD icon on quality select
 | 
				
			||||||
 | 
					* [Pull #209](https://github.com/fluid-player/fluid-player/pull/209) Advanced theatre mode
 | 
				
			||||||
 | 
					* [Pull #208](https://github.com/fluid-player/fluid-player/pull/208) Compress files
 | 
				
			||||||
 | 
					* [Pull #179](https://github.com/fluid-player/fluid-player/pull/179) Fix to prevent changing speed during ads
 | 
				
			||||||
 | 
					* [Pull #217](https://github.com/fluid-player/fluid-player/pull/217) Prevent video size change on quality switch
 | 
				
			||||||
 | 
					* [Pull #213](https://github.com/fluid-player/fluid-player/pull/213) Controls to stay working with adblock and fix for double event on mobile touch
 | 
				
			||||||
 | 
					* [Pull #212](https://github.com/fluid-player/fluid-player/pull/212) Poster image to fit player size
 | 
				
			||||||
 | 
					* [Pull #186](https://github.com/fluid-player/fluid-player/pull/186) Fix for source switch on Edge
 | 
				
			||||||
 | 
					* [Pull #219](https://github.com/fluid-player/fluid-player/pull/219) Play / pause icon fix and progress bar to disappear correctly
 | 
				
			||||||
 | 
					* [Pull #218](https://github.com/fluid-player/fluid-player/pull/218) Optional theatre settings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2.3.0
 | 
				
			||||||
 | 
					* [Pull #192](https://github.com/fluid-player/fluid-player/pull/191) Persist user settings across pages for volume, speed, quality and theatre mode
 | 
				
			||||||
 | 
					* [Pull #194](https://github.com/fluid-player/fluid-player/pull/194) Fix for play event on video click for certain devices
 | 
				
			||||||
 | 
					* [Pull #193](https://github.com/fluid-player/fluid-player/pull/193) Option to set adText and adTextPosition on a per ad basis
 | 
				
			||||||
 | 
					* [Pull #184](https://github.com/fluid-player/fluid-player/pull/184) Fix for thumbnails appearing incorrectly on mobile
 | 
				
			||||||
 | 
					* [Pull #181](https://github.com/fluid-player/fluid-player/pull/181) Fix for poster image for dash file
 | 
				
			||||||
 | 
					* [Pull #195](https://github.com/fluid-player/fluid-player/pull/195) Loading icon while player is waiting
 | 
				
			||||||
 | 
					* [Pull #200](https://github.com/fluid-player/fluid-player/pull/200) Ad text positioning fix
 | 
				
			||||||
 | 
					* [Pull #196](https://github.com/fluid-player/fluid-player/pull/196) Fix for issue causing controls to hide incorrectly
 | 
				
			||||||
 | 
					* [Pull #191](https://github.com/fluid-player/fluid-player/pull/191) Scrubbing to no longer trigger Fluid on.pause event
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2.2.2
 | 
				
			||||||
 | 
					* [Pull #175](https://github.com/fluid-player/fluid-player/pull/175) Fullscreen mode variable correct place
 | 
				
			||||||
 | 
					* [Pull #177](https://github.com/fluid-player/fluid-player/pull/177) Fix fadeOut/fadeIn opacity to correct values in the end of animation
 | 
				
			||||||
 | 
					* [Pull #180](https://github.com/fluid-player/fluid-player/pull/180) Adding VASTAdTagURI support
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2.2.1
 | 
				
			||||||
 | 
					* [Pull #153](https://github.com/fluid-player/fluid-player/pull/153) CDATA media file ignores whitespace correctly
 | 
				
			||||||
 | 
					* [Pull #154](https://github.com/fluid-player/fluid-player/pull/154) onPauseRoll not showing on source switch
 | 
				
			||||||
 | 
					* [Pull #155](https://github.com/fluid-player/fluid-player/pull/155) iOS native fullscreen
 | 
				
			||||||
 | 
					* [Pull #156](https://github.com/fluid-player/fluid-player/pull/156) CSS fixes for progress bar and logo
 | 
				
			||||||
 | 
					* [Pull #157](https://github.com/fluid-player/fluid-player/pull/157) Fix for onMainVideoEnded firing correctly
 | 
				
			||||||
 | 
					* [Pull #158](https://github.com/fluid-player/fluid-player/pull/158) Play / Pause animation to not show when changing source
 | 
				
			||||||
 | 
					* [Pull #159](https://github.com/fluid-player/fluid-player/pull/159) Theatre mode to not show in iframe
 | 
				
			||||||
 | 
					* [Pull #148](https://github.com/fluid-player/fluid-player/pull/148) Fix for currentTime being set for iOS and safari for ads and source switch
 | 
				
			||||||
 | 
					* [Pull #165](https://github.com/fluid-player/fluid-player/pull/165) Fix for video duration if passed as 00:00:00 in the VAST file
 | 
				
			||||||
 | 
					* [Pull #167](https://github.com/fluid-player/fluid-player/pull/167) Allow for individual images to be set in .vtt file
 | 
				
			||||||
 | 
					* [Pull #169](https://github.com/fluid-player/fluid-player/pull/169) Preview Thumbnail image locations - Ability to set relative path
 | 
				
			||||||
 | 
					* [Pull #168](https://github.com/fluid-player/fluid-player/pull/168) Show custom error if XML content-type is wrong
 | 
				
			||||||
 | 
					* [Pull #166](https://github.com/fluid-player/fluid-player/pull/166) Bug fix for Error 202  showing up periodically in the console
 | 
				
			||||||
 | 
					* [Pull #149](https://github.com/fluid-player/fluid-player/pull/149) Bug fix to remove mainVideoReady eventListener after success
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2.2.0
 | 
				
			||||||
 | 
					* [Pull #121](https://github.com/fluid-player/fluid-player/pull/121) 'Browser layout' VAST fixes
 | 
				
			||||||
 | 
					* [Pull #122](https://github.com/fluid-player/fluid-player/pull/122) iOS fullscreen improvements, use native player
 | 
				
			||||||
 | 
					* [Pull #125](https://github.com/fluid-player/fluid-player/pull/125) Fix for VAST tag: additional checks for CDATA node irregularity
 | 
				
			||||||
 | 
					* [Pull #126](https://github.com/fluid-player/fluid-player/pull/126) Pause player when linear ad opens (ad video is clicked)
 | 
				
			||||||
 | 
					* [Pull #127](https://github.com/fluid-player/fluid-player/pull/127) OnPause ad showing on source switch fix
 | 
				
			||||||
 | 
					* [Pull #128](https://github.com/fluid-player/fluid-player/pull/128) [Pull #139](https://github.com/fluid-player/fluid-player/pull/139) Poster Image as a param
 | 
				
			||||||
 | 
					* [Pull #130](https://github.com/fluid-player/fluid-player/pull/130) Create progressbar markers for nonLinear ads
 | 
				
			||||||
 | 
					* [Pull #131](https://github.com/fluid-player/fluid-player/pull/131) [Pull #136](https://github.com/fluid-player/fluid-player/pull/136/) Additional logo parameters
 | 
				
			||||||
 | 
					* [Pull #138](https://github.com/fluid-player/fluid-player/pull/138) Support for DASH and HLS streaming
 | 
				
			||||||
 | 
					* [Pull #143](https://github.com/fluid-player/fluid-player/pull/143) Positioning of ad and cta text elements
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2.1.2
 | 
				
			||||||
 | 
					* [Pull #108](https://github.com/fluid-player/fluid-player/pull/108) Fullscreen API call fix
 | 
				
			||||||
 | 
					* [Pull #110](https://github.com/fluid-player/fluid-player/pull/110) Improvements for iOs safari (use default skin) and mobile screens
 | 
				
			||||||
 | 
					* [Pull #111](https://github.com/fluid-player/fluid-player/pull/111) Adjust how iconClickThrough is gotten
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2.1.1
 | 
				
			||||||
 | 
					* [Pull #107](https://github.com/fluid-player/fluid-player/pull/107) Download and Theatre fixes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2.1
 | 
				
			||||||
 | 
					* [Pull #101](https://github.com/fluid-player/fluid-player/pull/101) Quality indicator
 | 
				
			||||||
 | 
					* [Pull #102](https://github.com/fluid-player/fluid-player/pull/102) API functions
 | 
				
			||||||
 | 
					* [Pull #103](https://github.com/fluid-player/fluid-player/pull/103) Landing page displayed in In-Stream ads
 | 
				
			||||||
 | 
					* [Pull #104](https://github.com/fluid-player/fluid-player/pull/104) Theater mode, download & playback rate
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2.0
 | 
				
			||||||
 | 
					* [Pull #91](https://github.com/fluid-player/fluid-player/pull/91) Version 2 Changes:
 | 
				
			||||||
 | 
					  * New default template
 | 
				
			||||||
 | 
					  * Add play button
 | 
				
			||||||
 | 
					  * Play pause animations
 | 
				
			||||||
 | 
					  * Restructuring of optional parameters
 | 
				
			||||||
 | 
					  * Remove templates
 | 
				
			||||||
 | 
					  * General fixes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 1.2.2
 | 
				
			||||||
 | 
					* [Pull #88](https://github.com/fluid-player/fluid-player/pull/88) Improve nonlinear ads
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 1.2.1
 | 
				
			||||||
 | 
					* [Pull #86](https://github.com/fluid-player/fluid-player/pull/86) [Pull #87](https://github.com/fluid-player/fluid-player/pull/87) Mid roll current time fix
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 1.2.0
 | 
				
			||||||
 | 
					* [Pull #68](https://github.com/fluid-player/fluid-player/pull/68) Controls remain fullscreen after escaping fullscreen
 | 
				
			||||||
 | 
					* [Pull #66](https://github.com/fluid-player/fluid-player/pull/66) Optional logoUrl for clickable logo
 | 
				
			||||||
 | 
					* [Pull #74](https://github.com/fluid-player/fluid-player/pull/74) Add ability to grab and slide the volume slider and timeline scrubber.
 | 
				
			||||||
 | 
					* [Pull #75](https://github.com/fluid-player/fluid-player/pull/75) [Pull #77](https://github.com/fluid-player/fluid-player/pull/77) Adding mid/post roll support and initial VAST nonLinear support.
 | 
				
			||||||
 | 
					* [Pull #67](https://github.com/fluid-player/fluid-player/pull/67) Adding key controls.
 | 
				
			||||||
 | 
					* [Pull #69](https://github.com/fluid-player/fluid-player/pull/69) Adding controls hiding functionality.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 1.1.3
 | 
				
			||||||
 | 
					* [Pull #50](https://github.com/fluid-player/fluid-player/pull/50) Fix for double double render of blank video on some browsers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 1.1.2
 | 
				
			||||||
 | 
					* [Pull #43](https://github.com/fluid-player/fluid-player/pull/43) Add two new skins.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 1.1.1
 | 
				
			||||||
 | 
					* [Pull #38](https://github.com/fluid-player/fluid-player/pull/38) Reset the CSS box-sizing settings.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 1.1.0
 | 
				
			||||||
 | 
					* [Pull #34](https://github.com/fluid-player/fluid-player/pull/34) Various Improvements:
 | 
				
			||||||
 | 
					  * Possibility to allow the user to switch between different video qualities. (Example, 720p, 1080p, etc...)
 | 
				
			||||||
 | 
					  * Enable/Disable autoplay.
 | 
				
			||||||
 | 
					  * Possibility to set a logo over the video player, with configurable position and opacity.
 | 
				
			||||||
 | 
					  * Possibility to show a text when a video ad is being played. (Example : "Advertising")
 | 
				
			||||||
 | 
					  * Possibility to show a call to action link when a video ad is being played. (Example : "Click here to learn more about this product.")
 | 
				
			||||||
 | 
					  * Improved CSS management.
 | 
				
			||||||
 | 
					  * Possibility to show a custom HTML code when the user pauses the video. (For example, a banner ad, or some related video links)
 | 
				
			||||||
 | 
					  * The video player can be fully responsive.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 1.0.2
 | 
				
			||||||
 | 
					* [Pull #18](https://github.com/fluid-player/fluid-player/pull/18) [Pull #19](https://github.com/fluid-player/fluid-player/pull/19) Update file names, add in min file versions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 1.0.1
 | 
				
			||||||
 | 
					* [Pull #1](https://github.com/fluid-player/fluid-player/pull/1) Fix a Fluid Player crash when the ad video file is not available. Properly announcing errors if an Error tag is present in the VAST tag.
 | 
				
			||||||
 | 
					* [Pull #3](https://github.com/fluid-player/fluid-player/pull/3) Demo layouts. Various bugfixes and improvements.
 | 
				
			||||||
 | 
					* [Pull #10](https://github.com/fluid-player/fluid-player/pull/10) Thumbnail previews from vtt file can be overwritten.
 | 
				
			||||||
 | 
					* [Pull #11](https://github.com/fluid-player/fluid-player/pull/11) Player shows current play time and video duration in 'default' template.
 | 
				
			||||||
 | 
					* [Pull #14](https://github.com/fluid-player/fluid-player/pull/14) Fix a minor issue when playing the video from outside the Fluid Player code.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 1.0.0
 | 
				
			||||||
 | 
					* Initial Release
 | 
				
			||||||
							
								
								
									
										54
									
								
								client/fluid-player/CONTRIBUTING.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,54 @@
 | 
				
			||||||
 | 
					# Contributing
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					If you are interested in making a contribution there are a few ways you could help out the project.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Filing issues
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[GitHub Issues](https://github.com/fluid-player/fluid-player/issues) are used for all discussions around the codebase, including **bugs** and **features**.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Reporting a Bug
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Good bug reports can be very helpful. A bug is a demonstrable problem with the code.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Guidelines for bug reports:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. Please use the [GitHub issue search](https://github.com/fluid-player/fluid-player/issues) — check if the issue has already been reported.
 | 
				
			||||||
 | 
					1. Check if the issue has already been fixed — try to reproduce it using the uncompressed code from latest `master` branch in the repository.
 | 
				
			||||||
 | 
					1. Create a small demo with the live example (reduced test case). You can possibly use [this codepen template](https://codepen.io/exadsleroy/pen/QWmWPeo) as a starting point -- don't forget to update it to the fluid-player version you use.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					A good bug report should be as detailed as possible, so that others won't have to follow up for the essential details.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**[File a bug report](https://github.com/fluid-player/fluid-player/issues/new)**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Requesting a Feature
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. [Search the issues](https://github.com/fluid-player/fluid-player/issues) for any previous requests for the same feature, and give a thumbs up or +1 on existing requests.
 | 
				
			||||||
 | 
					1. If no previous requests exist, create a new issue. Please be as clear as possible about why the feature is needed and the intended use case.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**[Request a feature](https://github.com/fluid-player/fluid-player/issues/new)**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Contributing code
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					If you plan to propose code changes it is required you create an
 | 
				
			||||||
 | 
					issue [issue](https://github.com/fluid-player/fluid-player/issues/new) with a brief proposal (as described in
 | 
				
			||||||
 | 
					Requesting a Feature) and discuss it with us first.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This is necessary to avoid more than one contributor working on the same feature/change and to avoid someone
 | 
				
			||||||
 | 
					spending time on feature/change that would not be merged for some reason.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					For smaller contributions just use this workflow:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* Create an issue describing the changes.
 | 
				
			||||||
 | 
					* Await confirmation from contributors.
 | 
				
			||||||
 | 
					* Fork the project.
 | 
				
			||||||
 | 
					* Create a branch for your feature or bug fix.
 | 
				
			||||||
 | 
					* Add code changes.
 | 
				
			||||||
 | 
					* All new features or changes to the player settings or interface have to be documented in the
 | 
				
			||||||
 | 
					[docs repo](https://github.com/fluid-player/fluid-player-docs), so that they are displayed
 | 
				
			||||||
 | 
					on [https://docs.fluidplayer.com](https://docs.fluidplayer.com).
 | 
				
			||||||
 | 
					If you have made changes like this, please fork fluid-player-docs as well and create a branch with the same
 | 
				
			||||||
 | 
					name as the feature branch, adding necessary changes to documentation.
 | 
				
			||||||
 | 
					* Send a pull request (both for fluid-player and fluid-player-docs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					After one of the contributors has checked and approved the changes, they will be merged into master branch
 | 
				
			||||||
 | 
					and will be included in the next release tag.
 | 
				
			||||||
							
								
								
									
										21
									
								
								client/fluid-player/LICENSE
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,21 @@
 | 
				
			||||||
 | 
					MIT License
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Copyright (c) 2020 Fluid Player and contributors
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
 | 
					of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
 | 
					in the Software without restriction, including without limitation the rights
 | 
				
			||||||
 | 
					to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
				
			||||||
 | 
					copies of the Software, and to permit persons to whom the Software is
 | 
				
			||||||
 | 
					furnished to do so, subject to the following conditions:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The above copyright notice and this permission notice shall be included in all
 | 
				
			||||||
 | 
					copies or substantial portions of the Software.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
				
			||||||
 | 
					IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
				
			||||||
 | 
					FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
				
			||||||
 | 
					AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
				
			||||||
 | 
					LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
				
			||||||
 | 
					OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
				
			||||||
 | 
					SOFTWARE.
 | 
				
			||||||
							
								
								
									
										23
									
								
								client/fluid-player/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,23 @@
 | 
				
			||||||
 | 
					# Fluid Player
 | 
				
			||||||
 | 
					[](https://github.com/fluid-player/fluid-player/releases/latest)
 | 
				
			||||||
 | 
					[](https://www.npmjs.com/package/fluid-player)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Version 3 released
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					A [new major version](https://github.com/fluid-player/fluid-player/pull/441) of Fluid Player has been released on May 20, 2020. Existing version 2 users are recommended to upgrade. [See quick setup guide](https://docs.fluidplayer.com/docs/integration/quick-setup/).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Overview
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Fluid Player is a free HTML5 video player. It is lightweight, easy to integrate and has advanced VAST capabilities.
 | 
				
			||||||
 | 
					The idea behind VAST, as well as the full VAST specification, can be found here: [VAST 4.0](https://www.iab.com/guidelines/digital-video-ad-serving-template-vast-4-0/).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Documentation
 | 
				
			||||||
 | 
					The integration and configuration of Fluid Player is fully outlined in [Fluid Player Documentation](http://docs.fluidplayer.com)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## License
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Fluid Player is licensed under the MIT License. View the [License File](LICENSE).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Changelog
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					A full list of changes and updates can be found in the project [CHANGELOG](CHANGELOG.md).
 | 
				
			||||||
							
								
								
									
										181
									
								
								client/fluid-player/e2e/ads_linear.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,181 @@
 | 
				
			||||||
 | 
					import { test, expect } from '@playwright/test';
 | 
				
			||||||
 | 
					import { waitForVideoToPlay, setVideoCurrentTime, getVideoCurrentTime } from './functions/video';
 | 
				
			||||||
 | 
					import { waitForSpecificNetworkCall } from './functions/network';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test.describe('desktop ads', () => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    test.beforeEach(async ({ page }) => {
 | 
				
			||||||
 | 
					        console.log(`Running ${test.info().title}`);
 | 
				
			||||||
 | 
					        await page.goto('/ads_linear.html');
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    test('should navigate to the publishers advertised website on click', async ({ page }) => {
 | 
				
			||||||
 | 
					        const fullPlayer = page.locator('#fluid_video_wrapper_fluid-player-e2e-case');
 | 
				
			||||||
 | 
					        const video = page.locator('video');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // start the video
 | 
				
			||||||
 | 
					        fullPlayer.click();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await waitForVideoToPlay(video);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Set up a listener for the 'popup' event
 | 
				
			||||||
 | 
					        // This listener listens for a new _blank tab to open
 | 
				
			||||||
 | 
					        const [popupPromise] = await Promise.all([
 | 
				
			||||||
 | 
					            page.waitForEvent('popup'), // Listen for the popup event
 | 
				
			||||||
 | 
					            fullPlayer.click() // click ad to open advertisers link
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Prevent the tab from fully opening
 | 
				
			||||||
 | 
					        const popup = popupPromise;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Verify the URL of the popup
 | 
				
			||||||
 | 
					        const popupUrl = popup.url();
 | 
				
			||||||
 | 
					        console.log(`Popup URL: ${popupUrl}`);
 | 
				
			||||||
 | 
					        expect(popupUrl).toBe('http://www.example.com/');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Close the popup to prevent extra tabs, in case the above failed to prevent the opening of the new tab
 | 
				
			||||||
 | 
					        await popup.close();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    test('should fire pre-, mid- and postRoll based on time', async ({ page }) => {
 | 
				
			||||||
 | 
					        const fullPlayer = page.locator('#fluid_video_wrapper_fluid-player-e2e-case');
 | 
				
			||||||
 | 
					        const skipButton = page.locator('.skip_button');
 | 
				
			||||||
 | 
					        const video = page.locator('video');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Start the video
 | 
				
			||||||
 | 
					        fullPlayer.click();
 | 
				
			||||||
 | 
					        await waitForVideoToPlay(video);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
					         * PREROLL
 | 
				
			||||||
 | 
					         */
 | 
				
			||||||
 | 
					        await expect(skipButton).toHaveText(/Skip ad in 2/);
 | 
				
			||||||
 | 
					        // Wait for skip ad timer
 | 
				
			||||||
 | 
					        await page.waitForTimeout(2500);
 | 
				
			||||||
 | 
					        await expect(skipButton).toHaveText(/Skip Ad /);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Skip the ad
 | 
				
			||||||
 | 
					        await skipButton.click();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
					         * MIDROLL
 | 
				
			||||||
 | 
					         */
 | 
				
			||||||
 | 
					        await page.waitForFunction(() => {
 | 
				
			||||||
 | 
					            const videoElement = document.querySelector('video') as HTMLVideoElement;
 | 
				
			||||||
 | 
					            // 15 is the length of the ad
 | 
				
			||||||
 | 
					            return videoElement && Math.floor(videoElement.duration) !== 15;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Midrolls don't trigger if you seek less then 5 seconds before their time
 | 
				
			||||||
 | 
					        await setVideoCurrentTime(video, 35);
 | 
				
			||||||
 | 
					        await page.waitForTimeout(5500);
 | 
				
			||||||
 | 
					        await expect(skipButton).toHaveText(/Skip ad in 2/);
 | 
				
			||||||
 | 
					        // Wait for skip ad timer
 | 
				
			||||||
 | 
					        await page.waitForTimeout(2500);
 | 
				
			||||||
 | 
					        await expect(skipButton).toHaveText(/Skip Ad /);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Skip the ad
 | 
				
			||||||
 | 
					        await skipButton.click();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await page.waitForTimeout(500);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await waitForVideoToPlay(video);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const currentTime = await getVideoCurrentTime(video);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Check if the video resumes after the midroll at the correct time
 | 
				
			||||||
 | 
					        expect(Math.floor(currentTime)).toEqual(39);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
					         * POSTROLL
 | 
				
			||||||
 | 
					         */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Skip to the end
 | 
				
			||||||
 | 
					        await page.waitForTimeout(500);
 | 
				
			||||||
 | 
					        await video.evaluate((videoEl) => {
 | 
				
			||||||
 | 
					            const vid = (videoEl as HTMLVideoElement);
 | 
				
			||||||
 | 
					            vid.currentTime = Math.max(0, vid.duration) - 1;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        await page.waitForTimeout(1000);
 | 
				
			||||||
 | 
					        await expect(skipButton).toHaveText(/Skip ad in 2/);
 | 
				
			||||||
 | 
					        // Wait for skip ad timer
 | 
				
			||||||
 | 
					        await page.waitForTimeout(2500);
 | 
				
			||||||
 | 
					        await expect(skipButton).toHaveText(/Skip Ad /);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await skipButton.waitFor({ state: 'visible', timeout: 5000 });
 | 
				
			||||||
 | 
					        // Skip the ad
 | 
				
			||||||
 | 
					        await skipButton.click();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Check if video is marked as ended
 | 
				
			||||||
 | 
					        await page.waitForFunction(() => {
 | 
				
			||||||
 | 
					            const video = document.querySelector('video');
 | 
				
			||||||
 | 
					            return video && !video.ended;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    test('ad should not be skipped when the ad countdown is not done', async ({ page }) => {
 | 
				
			||||||
 | 
					        const fullPlayer = page.locator('#fluid_video_wrapper_fluid-player-e2e-case');
 | 
				
			||||||
 | 
					        const skipButton = page.locator('.skip_button');
 | 
				
			||||||
 | 
					        const video = page.locator('video');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Start the video
 | 
				
			||||||
 | 
					        fullPlayer.click();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await page.waitForFunction(() => {
 | 
				
			||||||
 | 
					            const videoElement = document.querySelector('video');
 | 
				
			||||||
 | 
					            return videoElement && videoElement.duration > 0;
 | 
				
			||||||
 | 
					        }, { timeout: 5000 });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const adDuration = await video.evaluate((vid) => {
 | 
				
			||||||
 | 
					            const videoElement = vid as HTMLVideoElement;
 | 
				
			||||||
 | 
					            return videoElement.duration;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Click the button but it should not be skipped
 | 
				
			||||||
 | 
					        // NOTE: don't add 'await' because it will wait until it can skip
 | 
				
			||||||
 | 
					        skipButton.click();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // If the ad still has the same video duration, that means the video is not skipped
 | 
				
			||||||
 | 
					        const videoDurationAfterClick = await video.evaluate((vid) => {
 | 
				
			||||||
 | 
					            const videoElement = vid as HTMLVideoElement;
 | 
				
			||||||
 | 
					            return videoElement.duration;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(videoDurationAfterClick).not.toBeFalsy();
 | 
				
			||||||
 | 
					        expect(adDuration).not.toBeFalsy();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(videoDurationAfterClick).toEqual(adDuration);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await page.waitForTimeout(2000);
 | 
				
			||||||
 | 
					        // Skip Ad
 | 
				
			||||||
 | 
					        await skipButton.click();
 | 
				
			||||||
 | 
					        await page.waitForTimeout(500);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const videoDuration = await video.evaluate((vid) => {
 | 
				
			||||||
 | 
					            const videoElement = vid as HTMLVideoElement;
 | 
				
			||||||
 | 
					            return videoElement.duration;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(videoDuration).not.toBeFalsy();
 | 
				
			||||||
 | 
					        expect(adDuration).not.toBeFalsy();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(videoDuration).not.toEqual(adDuration);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    test('impression url should be called', async ({ page }) => {
 | 
				
			||||||
 | 
					        const fullPlayer = page.locator('#fluid_video_wrapper_fluid-player-e2e-case');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // start the video
 | 
				
			||||||
 | 
					        fullPlayer.click();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const request = await waitForSpecificNetworkCall(
 | 
				
			||||||
 | 
					            page,
 | 
				
			||||||
 | 
					            'http://www.example.com/impression',
 | 
				
			||||||
 | 
					            'GET'
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(request.url()).toBe('http://www.example.com/impression');
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										69
									
								
								client/fluid-player/e2e/controls.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,69 @@
 | 
				
			||||||
 | 
					import { test, expect } from '@playwright/test';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test.describe('desktop controls', () => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    test.beforeEach(async ({ page }) => {
 | 
				
			||||||
 | 
					        console.log(`Running ${test.info().title}`);
 | 
				
			||||||
 | 
					        await page.goto('/controls.html');
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    test('should toggle play/pause when clicking the player', async ({ page }) => {
 | 
				
			||||||
 | 
					        // Selectors
 | 
				
			||||||
 | 
					        const fullPlayer = page.locator('#fluid_video_wrapper_fluid-player-e2e-case');
 | 
				
			||||||
 | 
					        const playButton = page.locator('.fluid_button_play');
 | 
				
			||||||
 | 
					        const pauseButton = page.locator('.fluid_button_pause');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Initial state checks
 | 
				
			||||||
 | 
					        await expect(playButton).toBeVisible();
 | 
				
			||||||
 | 
					        await expect(pauseButton).not.toBeVisible();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Video player should start playing
 | 
				
			||||||
 | 
					        fullPlayer.click();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Wait for video to start playing
 | 
				
			||||||
 | 
					        await page.waitForFunction(() => {
 | 
				
			||||||
 | 
					            const video = document.querySelector('video');
 | 
				
			||||||
 | 
					            return video && !video.paused;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Verify playing state
 | 
				
			||||||
 | 
					        await expect(playButton).not.toBeVisible();
 | 
				
			||||||
 | 
					        await expect(pauseButton).toBeVisible();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Wait for 500ms so the browser doesn't reject the click
 | 
				
			||||||
 | 
					        await page.waitForTimeout(1000);
 | 
				
			||||||
 | 
					        // Video player should pause
 | 
				
			||||||
 | 
					        fullPlayer.click();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Wait for video to pause
 | 
				
			||||||
 | 
					        await page.waitForFunction(() => {
 | 
				
			||||||
 | 
					            const video = document.querySelector('video');
 | 
				
			||||||
 | 
					            return video && video.paused;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Verify paused state
 | 
				
			||||||
 | 
					        await expect(playButton).toBeVisible();
 | 
				
			||||||
 | 
					        await expect(pauseButton).not.toBeVisible();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    test('mouse should disappear when hovering the video', async ({ page }) => {
 | 
				
			||||||
 | 
					        const video = page.locator('video');
 | 
				
			||||||
 | 
					        const playButton = page.locator('.fluid_button_play');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await playButton.click();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Hover over the video
 | 
				
			||||||
 | 
					        await video.hover();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await page.waitForTimeout(1500);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Evaluate the cursor CSS property of the video element or its parent
 | 
				
			||||||
 | 
					        const isCursorHidden = await video.evaluate((vid) => {
 | 
				
			||||||
 | 
					            const computedStyle = window.getComputedStyle(vid);
 | 
				
			||||||
 | 
					            return computedStyle.cursor === 'none';
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(isCursorHidden).toBeTruthy(); // Assert that the cursor is hidden
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										21
									
								
								client/fluid-player/e2e/functions/network.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,21 @@
 | 
				
			||||||
 | 
					import { Page, Request } from 'playwright';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Wait for a specific network request and log it.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param page - The Playwright page instance.
 | 
				
			||||||
 | 
					 * @param url - The URL of the request to wait for.
 | 
				
			||||||
 | 
					 * @param method - The HTTP method of the request (default is 'GET').
 | 
				
			||||||
 | 
					 * @returns The intercepted request object.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export async function waitForSpecificNetworkCall(
 | 
				
			||||||
 | 
					    page: Page,
 | 
				
			||||||
 | 
					    url: string,
 | 
				
			||||||
 | 
					    method: string = 'GET'
 | 
				
			||||||
 | 
					): Promise<Request> {
 | 
				
			||||||
 | 
					    const request = await page.waitForRequest((req) =>
 | 
				
			||||||
 | 
					        req.url() === url && req.method() === method
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return request;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										86
									
								
								client/fluid-player/e2e/functions/video.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,86 @@
 | 
				
			||||||
 | 
					import { Locator, Page } from 'playwright';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Seek to a given time in the video
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param video - Playwright video locator
 | 
				
			||||||
 | 
					 * @param time - The time you want to seek to
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export async function setVideoCurrentTime(video: Locator, time: number): Promise<void> {
 | 
				
			||||||
 | 
					    await video.page().waitForFunction(
 | 
				
			||||||
 | 
					        (vid) => {
 | 
				
			||||||
 | 
					            const videoElement = vid as HTMLVideoElement | null;
 | 
				
			||||||
 | 
					            return videoElement && videoElement.readyState >= 2;
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        await video.elementHandle(),
 | 
				
			||||||
 | 
					        { timeout: 10000 }
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Seek to the specified time
 | 
				
			||||||
 | 
					    await video.evaluate((vid, t) => {
 | 
				
			||||||
 | 
					        const videoElement = vid as HTMLVideoElement;
 | 
				
			||||||
 | 
					        videoElement.currentTime = t;
 | 
				
			||||||
 | 
					    }, time);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Wait until the video duration has changed
 | 
				
			||||||
 | 
					 * This way you can detect if the ad or content is loaded in
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param page - The Playwright page instance
 | 
				
			||||||
 | 
					 * @param initialDuration - The initial duration of the video element
 | 
				
			||||||
 | 
					 * @param timeout
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export async function waitForVideoDurationChange(
 | 
				
			||||||
 | 
					    page: Page,
 | 
				
			||||||
 | 
					    initialDuration: number,
 | 
				
			||||||
 | 
					    timeout: number = 10000
 | 
				
			||||||
 | 
					): Promise<void> {
 | 
				
			||||||
 | 
					    await page.waitForFunction(
 | 
				
			||||||
 | 
					        (initialDur) => {
 | 
				
			||||||
 | 
					            const videoElement = document.querySelector('video') as HTMLVideoElement;
 | 
				
			||||||
 | 
					            return videoElement.duration !== initialDur;
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        initialDuration,
 | 
				
			||||||
 | 
					        { timeout }
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Get the current duration of the video
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param video - Playwright video locator
 | 
				
			||||||
 | 
					 * @returns video duration time
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export async function getVideoDuration(video: Locator): Promise<number> {
 | 
				
			||||||
 | 
					    return await video.evaluate((vid) => {
 | 
				
			||||||
 | 
					        const videoElement = vid as HTMLVideoElement;
 | 
				
			||||||
 | 
					        return videoElement.duration;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Get the current time of the video
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param video - Playwright video locator
 | 
				
			||||||
 | 
					 * @returns video current time
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export async function getVideoCurrentTime(video: Locator): Promise<number> {
 | 
				
			||||||
 | 
					    return await video.evaluate((vid) => {
 | 
				
			||||||
 | 
					        const videoElement = vid as HTMLVideoElement;
 | 
				
			||||||
 | 
					        return videoElement.currentTime;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Waits until the given video element starts playing.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param video - The Playwright Locator for the video element.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export async function waitForVideoToPlay(video: Locator): Promise<void> {
 | 
				
			||||||
 | 
					    await video.evaluate((vid) => {
 | 
				
			||||||
 | 
					        return new Promise<void>((resolve) => {
 | 
				
			||||||
 | 
					            vid.addEventListener('playing', () => resolve(), { once: true });
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								client/fluid-player/e2e/snapshots/baseline-sv-grid.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 479 KiB  | 
							
								
								
									
										37
									
								
								client/fluid-player/e2e/suggested_videos.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,37 @@
 | 
				
			||||||
 | 
					import { test, expect } from '@playwright/test';
 | 
				
			||||||
 | 
					import { waitForVideoToPlay, setVideoCurrentTime, getVideoDuration } from './functions/video';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test.describe('suggested videos', () => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    test.beforeEach(async ({ page }) => {
 | 
				
			||||||
 | 
					        console.log(`Running ${test.info().title}`);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    test('should show up in 4x3 grid at the end of the video if it is configured and after postRoll', async ({ page }) => {
 | 
				
			||||||
 | 
					        await page.goto('/suggested_videos_e2e.html');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const fullPlayer = page.locator('#fluid_video_wrapper_fluid-player-e2e-case');
 | 
				
			||||||
 | 
					        const video = page.locator('video');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        fullPlayer.click();
 | 
				
			||||||
 | 
					        await waitForVideoToPlay(video);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const videoDuration = await getVideoDuration(video);
 | 
				
			||||||
 | 
					        await setVideoCurrentTime(video, videoDuration - 5);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await waitForVideoToPlay(video);
 | 
				
			||||||
 | 
					        await page.waitForTimeout(5500);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const suggestedVideosGrid = page.locator('.suggested_tile_grid');
 | 
				
			||||||
 | 
					        await expect(suggestedVideosGrid).not.toBeVisible();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await page.waitForTimeout(3000);
 | 
				
			||||||
 | 
					        const skipButton = page.locator('.skip_button');
 | 
				
			||||||
 | 
					        skipButton.click();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await expect(suggestedVideosGrid).toBeVisible();
 | 
				
			||||||
 | 
					        expect(await suggestedVideosGrid.screenshot()).toMatchSnapshot('baseline-sv-grid.png', { threshold: 0.02 });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										7233
									
								
								client/fluid-player/package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										60
									
								
								client/fluid-player/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,60 @@
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "name": "fluid-player",
 | 
				
			||||||
 | 
					  "version": "3.46.0",
 | 
				
			||||||
 | 
					  "description": "Fluid Player is a free HTML5 video player",
 | 
				
			||||||
 | 
					  "main": "src/index.js",
 | 
				
			||||||
 | 
					  "scripts": {
 | 
				
			||||||
 | 
					    "build": "webpack --mode=production",
 | 
				
			||||||
 | 
					    "build-cdn": "webpack --mode=production --env dist='current' && webpack --mode=production --env dist='versioned'",
 | 
				
			||||||
 | 
					    "build-dev": "webpack --mode=development",
 | 
				
			||||||
 | 
					    "build-dev-debug": "webpack --mode=development --debug",
 | 
				
			||||||
 | 
					    "dev-server": "webpack serve --mode=development --host 0.0.0.0",
 | 
				
			||||||
 | 
					    "dev-server-debug": "webpack serve --mode=development --env debug --host 0.0.0.0",
 | 
				
			||||||
 | 
					    "dev-server-test": "webpack serve --mode=production --no-live-reload --host 0.0.0.0",
 | 
				
			||||||
 | 
					    "start": "npm run dev-server-debug",
 | 
				
			||||||
 | 
					    "e2e-ui": "npx playwright test --ui",
 | 
				
			||||||
 | 
					    "e2e-report": "npx playwright show-report",
 | 
				
			||||||
 | 
					    "e2e": "npx playwright test"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "repository": {
 | 
				
			||||||
 | 
					    "type": "git",
 | 
				
			||||||
 | 
					    "url": "git+https://github.com/fluid-player/fluid-player.git"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "author": "EXOGROUP <info@exogroup.com>",
 | 
				
			||||||
 | 
					  "license": "MIT",
 | 
				
			||||||
 | 
					  "bugs": {
 | 
				
			||||||
 | 
					    "url": "https://github.com/fluid-player/fluid-player/issues"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "homepage": "https://fluidplayer.com",
 | 
				
			||||||
 | 
					  "com_fluidplayer": {
 | 
				
			||||||
 | 
					    "cdn": "https://cdn.fluidplayer.com"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "browserslist": [
 | 
				
			||||||
 | 
					    "> 0.25%",
 | 
				
			||||||
 | 
					    "not dead",
 | 
				
			||||||
 | 
					    "IE 11"
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "dependencies": {
 | 
				
			||||||
 | 
					    "dashjs": "^4.5.2",
 | 
				
			||||||
 | 
					    "es6-promise": "^4.2.8",
 | 
				
			||||||
 | 
					    "hls.js": "^1.5.13",
 | 
				
			||||||
 | 
					    "panolens": "^0.12.1",
 | 
				
			||||||
 | 
					    "videojs-vtt.js": "^0.15.4"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "devDependencies": {
 | 
				
			||||||
 | 
					    "@babel/core": "^7.20.12",
 | 
				
			||||||
 | 
					    "@babel/preset-env": "^7.20.2",
 | 
				
			||||||
 | 
					    "@playwright/test": "^1.49.0",
 | 
				
			||||||
 | 
					    "@types/node": "^22.9.1",
 | 
				
			||||||
 | 
					    "babel-loader": "^9.1.2",
 | 
				
			||||||
 | 
					    "cheerio": "^1.0.0-rc.3",
 | 
				
			||||||
 | 
					    "copy-webpack-plugin": "^11.0.0",
 | 
				
			||||||
 | 
					    "css-loader": "^6.7.3",
 | 
				
			||||||
 | 
					    "html-webpack-plugin": "^5.5.0",
 | 
				
			||||||
 | 
					    "semver": "^7.3.2",
 | 
				
			||||||
 | 
					    "style-loader": "^3.3.1",
 | 
				
			||||||
 | 
					    "webpack": "^5.75.0",
 | 
				
			||||||
 | 
					    "webpack-cli": "^5.1.1",
 | 
				
			||||||
 | 
					    "webpack-dev-server": "^4.11.1"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										73
									
								
								client/fluid-player/playwright.config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,73 @@
 | 
				
			||||||
 | 
					import { defineConfig, devices } from '@playwright/test';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * See https://playwright.dev/docs/test-configuration.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export default defineConfig({
 | 
				
			||||||
 | 
					  testDir: './e2e',
 | 
				
			||||||
 | 
					  /* Run tests in files in parallel */
 | 
				
			||||||
 | 
					  fullyParallel: true,
 | 
				
			||||||
 | 
					  /* Fail the build on CI if you accidentally left test.only in the source code. */
 | 
				
			||||||
 | 
					  forbidOnly: !!process.env.CI,
 | 
				
			||||||
 | 
					  /* Retry on CI only */
 | 
				
			||||||
 | 
					  retries: process.env.CI ? 2 : 0,
 | 
				
			||||||
 | 
					  /* Opt out of parallel tests on CI. */
 | 
				
			||||||
 | 
					  workers: process.env.CI ? 1 : undefined,
 | 
				
			||||||
 | 
					  /* Reporter to use. See https://playwright.dev/docs/test-reporters */
 | 
				
			||||||
 | 
					  reporter: 'html',
 | 
				
			||||||
 | 
					  /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
 | 
				
			||||||
 | 
					  use: {
 | 
				
			||||||
 | 
					    /* Base URL to use in actions like `await page.goto('/')`. */
 | 
				
			||||||
 | 
					    baseURL: 'http://localhost:8080',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
 | 
				
			||||||
 | 
					    trace: 'on-first-retry',
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /* Configure projects for major browsers */
 | 
				
			||||||
 | 
					  projects: [
 | 
				
			||||||
 | 
					    // {
 | 
				
			||||||
 | 
					    //   name: 'chromium',
 | 
				
			||||||
 | 
					    //   use: { ...devices['Desktop Chrome'] },
 | 
				
			||||||
 | 
					    // },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      name: 'firefox',
 | 
				
			||||||
 | 
					      use: { ...devices['Desktop Firefox'] },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // {
 | 
				
			||||||
 | 
					    //   name: 'webkit',
 | 
				
			||||||
 | 
					    //   use: { ...devices['Desktop Safari'] },
 | 
				
			||||||
 | 
					    // },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* Test against mobile viewports. */
 | 
				
			||||||
 | 
					    // {
 | 
				
			||||||
 | 
					    //   name: 'Mobile Chrome',
 | 
				
			||||||
 | 
					    //   use: { ...devices['Pixel 5'] },
 | 
				
			||||||
 | 
					    // },
 | 
				
			||||||
 | 
					    // {
 | 
				
			||||||
 | 
					    //   name: 'Mobile Safari',
 | 
				
			||||||
 | 
					    //   use: { ...devices['iPhone 12'] },
 | 
				
			||||||
 | 
					    // },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* Test against branded browsers. */
 | 
				
			||||||
 | 
					    // {
 | 
				
			||||||
 | 
					    //   name: 'Microsoft Edge',
 | 
				
			||||||
 | 
					    //   use: { ...devices['Desktop Edge'], channel: 'msedge' },
 | 
				
			||||||
 | 
					    // },
 | 
				
			||||||
 | 
					    // {
 | 
				
			||||||
 | 
					    //   name: 'Google Chrome',
 | 
				
			||||||
 | 
					    //   use: { ...devices['Desktop Chrome'], channel: 'chrome' },
 | 
				
			||||||
 | 
					    // },
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  snapshotPathTemplate: '{testDir}/snapshots/{arg}{ext}', // Use the specified filename
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /* Run your local dev server before starting the tests */
 | 
				
			||||||
 | 
					  // webServer: {
 | 
				
			||||||
 | 
					  //   command: 'npm run start',
 | 
				
			||||||
 | 
					  //   url: 'http://127.0.0.1:3000',
 | 
				
			||||||
 | 
					  //   reuseExistingServer: !process.env.CI,
 | 
				
			||||||
 | 
					  // },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										19
									
								
								client/fluid-player/src/browser.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,19 @@
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Build entry point for CDN builds.
 | 
				
			||||||
 | 
					 * You SHOULD NOT import this file except if you plan to build browser distribution of Fluid Player.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import fluidPlayerInitializer from './index';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Import CSS automatically in browser builds.
 | 
				
			||||||
 | 
					import './css/fluidplayer.css';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (window) {
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Register public interface.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    if (!window.fluidPlayer) {
 | 
				
			||||||
 | 
					        window.fluidPlayer = fluidPlayerInitializer;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										1590
									
								
								client/fluid-player/src/css/fluidplayer.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										143
									
								
								client/fluid-player/src/css/suggestedVideos.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,143 @@
 | 
				
			||||||
 | 
					.suggested_tile_grid {
 | 
				
			||||||
 | 
					    --thumbnail-height: 120px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.suggested_tile_grid {
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    left: 0;
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    z-index: 100;
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    height: var(--thumbnail-height);
 | 
				
			||||||
 | 
					    overflow-x: auto;
 | 
				
			||||||
 | 
					    overflow-y: hidden;
 | 
				
			||||||
 | 
					    justify-content: flex-start;
 | 
				
			||||||
 | 
					    bottom: 53px;
 | 
				
			||||||
 | 
					    top: initial;
 | 
				
			||||||
 | 
					    column-gap: 10px;
 | 
				
			||||||
 | 
					    row-gap: 10px;
 | 
				
			||||||
 | 
					    white-space: nowrap;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.suggested_tile {
 | 
				
			||||||
 | 
					    aspect-ratio: 16/9;
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    justify-content: center;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    border-radius: 2px;
 | 
				
			||||||
 | 
					    flex-shrink: 0;
 | 
				
			||||||
 | 
					    width: calc(var(--thumbnail-height) * (16/9));
 | 
				
			||||||
 | 
					    height: var(--thumbnail-height);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    img {
 | 
				
			||||||
 | 
					        height: -webkit-fill-available;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.suggested_tile_image {
 | 
				
			||||||
 | 
					    max-width: 100%;
 | 
				
			||||||
 | 
					    max-height: 100%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.suggested_tile:first-child {
 | 
				
			||||||
 | 
					    margin-left: 20px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.suggested_tile:last-child {
 | 
				
			||||||
 | 
					    margin-right: 20px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.suggested_tile_overlay {
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    height: 100%;
 | 
				
			||||||
 | 
					    background: rgba(0, 0, 0, 0.6);
 | 
				
			||||||
 | 
					    color: #ffffff;
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    flex-direction: column;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					    justify-content: center;
 | 
				
			||||||
 | 
					    opacity: 0;
 | 
				
			||||||
 | 
					    transition: opacity 0.25s;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.suggested_tile_overlay .suggested_tile_title {
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    bottom: 0px;
 | 
				
			||||||
 | 
					    left: 10px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.suggested_tile_overlay--blur {
 | 
				
			||||||
 | 
					    backdrop-filter: blur(5px);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.suggested_tile_overlay>* {
 | 
				
			||||||
 | 
					    transform: translateY(20px);
 | 
				
			||||||
 | 
					    transition: transform 0.25s;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.suggested_tile_overlay:hover {
 | 
				
			||||||
 | 
					    opacity: 1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.suggested_tile_overlay:hover>* {
 | 
				
			||||||
 | 
					    transform: translateY(0);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.suggested_tile:hover {
 | 
				
			||||||
 | 
					    cursor: pointer;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@media only screen and (max-width: 600px) and (orientation: portrait) {
 | 
				
			||||||
 | 
					    .suggested_tile_grid {
 | 
				
			||||||
 | 
					        display: none;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Medium devices like tablet portrait */
 | 
				
			||||||
 | 
					@media only screen and (min-width: 992px) {
 | 
				
			||||||
 | 
					    .suggested_tile_grid {
 | 
				
			||||||
 | 
					        position: absolute;
 | 
				
			||||||
 | 
					        left: 0;
 | 
				
			||||||
 | 
					        top: 0;
 | 
				
			||||||
 | 
					        width: 100%;
 | 
				
			||||||
 | 
					        height: calc(100% - 53px);
 | 
				
			||||||
 | 
					        display: grid;
 | 
				
			||||||
 | 
					        grid-template-columns: repeat(3, 20%);
 | 
				
			||||||
 | 
					        grid-template-rows: min-content min-content;
 | 
				
			||||||
 | 
					        column-gap: 40px;
 | 
				
			||||||
 | 
					        row-gap: 10px;
 | 
				
			||||||
 | 
					        z-index: 100;
 | 
				
			||||||
 | 
					        align-content: center;
 | 
				
			||||||
 | 
					        justify-content: center;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* hide the last 6 video tiles */
 | 
				
			||||||
 | 
					    .suggested_tile:nth-child(n+7) {
 | 
				
			||||||
 | 
					      display: none;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .suggested_tile:first-child {
 | 
				
			||||||
 | 
					        margin-left: 0px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .suggested_tile:last-child {
 | 
				
			||||||
 | 
					        margin-right: 0px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Desktop */
 | 
				
			||||||
 | 
					@media only screen and (min-width: 1200px) {
 | 
				
			||||||
 | 
					    .suggested_tile_grid {
 | 
				
			||||||
 | 
					        grid-template-columns: repeat(4, 20%);
 | 
				
			||||||
 | 
					        column-gap: 10px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    .suggested_tile {
 | 
				
			||||||
 | 
					        width: initial;
 | 
				
			||||||
 | 
					        height: initial;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    .suggested_tile:nth-child(n+7) {
 | 
				
			||||||
 | 
					        display: flex;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										3469
									
								
								client/fluid-player/src/fluidplayer.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										222
									
								
								client/fluid-player/src/index.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,222 @@
 | 
				
			||||||
 | 
					declare module 'fluid-player' {
 | 
				
			||||||
 | 
					    function fluidPlayer(
 | 
				
			||||||
 | 
					        target: HTMLVideoElement | String | string,
 | 
				
			||||||
 | 
					        options?: Partial<FluidPlayerOptions>
 | 
				
			||||||
 | 
					    ): FluidPlayerInstance;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    export default fluidPlayer;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					declare type AdditionalEventInfo = { mediaSourceType: 'ad' | 'source' };
 | 
				
			||||||
 | 
					declare type OnPlay = (event: 'play', callback: (additionalInfo: AdditionalEventInfo) => void) => void;
 | 
				
			||||||
 | 
					declare type OnPlaying =
 | 
				
			||||||
 | 
					    (event: 'playing', callback: (event: Event, additionalInfo: AdditionalEventInfo) => void) => void;
 | 
				
			||||||
 | 
					declare type OnPause = (event: 'pause', callback: (additionalInfo: AdditionalEventInfo) => void) => void;
 | 
				
			||||||
 | 
					declare type OnEnded = (event: 'ended', callback: (additionalInfo: AdditionalEventInfo) => void) => void;
 | 
				
			||||||
 | 
					declare type OnSeeked = (event: 'seeked', callback: (additionalInfo: AdditionalEventInfo) => void) => void;
 | 
				
			||||||
 | 
					declare type OnTheaterModeOn =
 | 
				
			||||||
 | 
					    (event: 'theatreModeOn', callback: (event: Event, additionalInfo: AdditionalEventInfo) => void) => void;
 | 
				
			||||||
 | 
					declare type OnTheaterModeOff =
 | 
				
			||||||
 | 
					    (event: 'theatreModeOff', callback: (event: Event, additionalInfo: AdditionalEventInfo) => void) => void;
 | 
				
			||||||
 | 
					declare type OnTimeUpdate =
 | 
				
			||||||
 | 
					    (event: 'timeupdate', callback: (time: number, additionalInfo: AdditionalEventInfo) => void) => void;
 | 
				
			||||||
 | 
					declare type OnMiniPlayerToggle =
 | 
				
			||||||
 | 
					    (event: 'miniPlayerToggle', callback: (event: CustomEvent<{
 | 
				
			||||||
 | 
					        isToggledOn: boolean
 | 
				
			||||||
 | 
					    }>, additionalInfo: AdditionalEventInfo) => void) => void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					declare interface FluidPlayerInstance {
 | 
				
			||||||
 | 
					    play: () => void;
 | 
				
			||||||
 | 
					    pause: () => void;
 | 
				
			||||||
 | 
					    skipTo: (seconds: number) => void;
 | 
				
			||||||
 | 
					    setPlaybackSpeed: (speed: number) => void;
 | 
				
			||||||
 | 
					    setVolume: (volume: number) => void;
 | 
				
			||||||
 | 
					    toggleControlBar: (shouldToggle: boolean) => void;
 | 
				
			||||||
 | 
					    toggleFullScreen: (shouldToggle: boolean) => void;
 | 
				
			||||||
 | 
					    toggleMiniPlayer: (shouldToggle: boolean) => void;
 | 
				
			||||||
 | 
					    setHtmlOnPauseBlock: (pauseBlock: { html: string; width: number; height: number; }) => void;
 | 
				
			||||||
 | 
					    destroy: () => void;
 | 
				
			||||||
 | 
					    dashInstance: () => any | null;
 | 
				
			||||||
 | 
					    hlsInstance: () => any | null;
 | 
				
			||||||
 | 
					    on: OnPlay & OnPlaying & OnPause & OnEnded & OnSeeked & OnTheaterModeOn & OnTheaterModeOff & OnTimeUpdate &
 | 
				
			||||||
 | 
					        OnMiniPlayerToggle;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					declare interface LayoutControls {
 | 
				
			||||||
 | 
					    primaryColor: false | string;
 | 
				
			||||||
 | 
					    posterImage: false | string;
 | 
				
			||||||
 | 
					    posterImageSize: 'auto' | 'cover' | 'contain';
 | 
				
			||||||
 | 
					    playButtonShowing: boolean;
 | 
				
			||||||
 | 
					    playPauseAnimation: boolean;
 | 
				
			||||||
 | 
					    fillToContainer: boolean;
 | 
				
			||||||
 | 
					    autoPlay: boolean;
 | 
				
			||||||
 | 
					    preload: 'none' | 'metadata' | 'auto' | string;
 | 
				
			||||||
 | 
					    mute: boolean;
 | 
				
			||||||
 | 
					    doubleclickFullscreen: boolean;
 | 
				
			||||||
 | 
					    subtitlesEnabled: boolean;
 | 
				
			||||||
 | 
					    keyboardControl: boolean;
 | 
				
			||||||
 | 
					    title: string;
 | 
				
			||||||
 | 
					    loop: boolean;
 | 
				
			||||||
 | 
					    logo: Partial<{
 | 
				
			||||||
 | 
					        imageUrl: string | null;
 | 
				
			||||||
 | 
					        position: 'top right' | 'top left' | 'bottom right' | 'bottom left';
 | 
				
			||||||
 | 
					        clickUrl: string | null;
 | 
				
			||||||
 | 
					        opacity: number;
 | 
				
			||||||
 | 
					        mouseOverImageUrl: string | null;
 | 
				
			||||||
 | 
					        imageMargin: string;
 | 
				
			||||||
 | 
					        hideWithControls: boolean;
 | 
				
			||||||
 | 
					        showOverAds: boolean;
 | 
				
			||||||
 | 
					    }>;
 | 
				
			||||||
 | 
					    controlBar: Partial<{
 | 
				
			||||||
 | 
					        autoHide: boolean;
 | 
				
			||||||
 | 
					        autoHideTimeout: number;
 | 
				
			||||||
 | 
					        animated: boolean;
 | 
				
			||||||
 | 
					    }>;
 | 
				
			||||||
 | 
					    timelinePreview: VTTPreviewOptions | StaticPreviewOptions;
 | 
				
			||||||
 | 
					    htmlOnPauseBlock: Partial<{
 | 
				
			||||||
 | 
					        html: string | null;
 | 
				
			||||||
 | 
					        height: number | null;
 | 
				
			||||||
 | 
					        width: number | null;
 | 
				
			||||||
 | 
					    }>;
 | 
				
			||||||
 | 
					    layout: 'default' | string;
 | 
				
			||||||
 | 
					    allowDownload: boolean;
 | 
				
			||||||
 | 
					    playbackRateEnabled: boolean;
 | 
				
			||||||
 | 
					    allowTheatre: boolean;
 | 
				
			||||||
 | 
					    theatreAdvanced: Partial<{
 | 
				
			||||||
 | 
					        theatreElement: string;
 | 
				
			||||||
 | 
					        classToApply: string;
 | 
				
			||||||
 | 
					    }>;
 | 
				
			||||||
 | 
					    theatreSettings: Partial<{
 | 
				
			||||||
 | 
					        width: string;
 | 
				
			||||||
 | 
					        height: string;
 | 
				
			||||||
 | 
					        marginTop: number;
 | 
				
			||||||
 | 
					        horizontalAlign: 'center' | 'left' | 'right';
 | 
				
			||||||
 | 
					    }>;
 | 
				
			||||||
 | 
					    playerInitCallback: () => void;
 | 
				
			||||||
 | 
					    persistentSettings: Partial<{
 | 
				
			||||||
 | 
					        volume: boolean;
 | 
				
			||||||
 | 
					        quality: boolean;
 | 
				
			||||||
 | 
					        speed: boolean;
 | 
				
			||||||
 | 
					        theatre: boolean;
 | 
				
			||||||
 | 
					    }>;
 | 
				
			||||||
 | 
					    controlForwardBackward: Partial<{
 | 
				
			||||||
 | 
					        show: boolean;
 | 
				
			||||||
 | 
					        doubleTapMobile: boolean;
 | 
				
			||||||
 | 
					    }>;
 | 
				
			||||||
 | 
					    contextMenu: Partial<{
 | 
				
			||||||
 | 
					        controls: boolean;
 | 
				
			||||||
 | 
					        links: Array<{
 | 
				
			||||||
 | 
					            href: string;
 | 
				
			||||||
 | 
					            label: string;
 | 
				
			||||||
 | 
					        }>;
 | 
				
			||||||
 | 
					    }>;
 | 
				
			||||||
 | 
					    miniPlayer: Partial<{
 | 
				
			||||||
 | 
					        enabled: boolean;
 | 
				
			||||||
 | 
					        width: number;
 | 
				
			||||||
 | 
					        height: number;
 | 
				
			||||||
 | 
					        widthMobile: number;
 | 
				
			||||||
 | 
					        placeholderText: string;
 | 
				
			||||||
 | 
					        position: 'top right' | 'top left' | 'bottom right' | 'bottom left';
 | 
				
			||||||
 | 
					        autoToggle: boolean;
 | 
				
			||||||
 | 
					    }>;
 | 
				
			||||||
 | 
					    showCardBoardView: boolean;
 | 
				
			||||||
 | 
					    showCardBoardJoystick: boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					declare interface VTTPreviewOptions {
 | 
				
			||||||
 | 
					    file: string;
 | 
				
			||||||
 | 
					    type: 'VTT';
 | 
				
			||||||
 | 
					    spriteRelativePath?: boolean;
 | 
				
			||||||
 | 
					    sprite?: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					declare interface StaticPreviewOptions {
 | 
				
			||||||
 | 
					    type: 'static';
 | 
				
			||||||
 | 
					    frames: Array<{
 | 
				
			||||||
 | 
					        startTime: number;
 | 
				
			||||||
 | 
					        endTime: number;
 | 
				
			||||||
 | 
					        image: string;
 | 
				
			||||||
 | 
					        x: number;
 | 
				
			||||||
 | 
					        y: number;
 | 
				
			||||||
 | 
					        w: number;
 | 
				
			||||||
 | 
					        h: number;
 | 
				
			||||||
 | 
					    }>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					declare interface VastOptions {
 | 
				
			||||||
 | 
					    adList: Array<PreRollAdOptions | MidRollAdOptions | PostRollAdOptions | OnPauseRollAdOptions>;
 | 
				
			||||||
 | 
					    skipButtonCaption: string;
 | 
				
			||||||
 | 
					    skipButtonClickCaption: string;
 | 
				
			||||||
 | 
					    adText: string;
 | 
				
			||||||
 | 
					    adTextPosition: 'top right' | 'top left' | 'bottom right' | 'bottom left';
 | 
				
			||||||
 | 
					    adCTAText: string | boolean;
 | 
				
			||||||
 | 
					    adCTATextPosition: 'top right' | 'top left' | 'bottom right' | 'bottom left';
 | 
				
			||||||
 | 
					    adCTATextVast: boolean;
 | 
				
			||||||
 | 
					    vastTimeout: number;
 | 
				
			||||||
 | 
					    showPlayButton: boolean;
 | 
				
			||||||
 | 
					    maxAllowedVastTagRedirects: number;
 | 
				
			||||||
 | 
					    showProgressbarMarkers: boolean;
 | 
				
			||||||
 | 
					    adClickable: boolean;
 | 
				
			||||||
 | 
					    allowVPAID: boolean;
 | 
				
			||||||
 | 
					    vastAdvanced: Partial<{
 | 
				
			||||||
 | 
					        vastLoadedCallback: () => void;
 | 
				
			||||||
 | 
					        noVastVideoCallback: () => void;
 | 
				
			||||||
 | 
					        vastVideoSkippedCallback: () => void;
 | 
				
			||||||
 | 
					        vastVideoEndedCallback: () => void;
 | 
				
			||||||
 | 
					    }>;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					declare interface AdOptions {
 | 
				
			||||||
 | 
					    vastTag: string;
 | 
				
			||||||
 | 
					    roll: string;
 | 
				
			||||||
 | 
					    fallbackVastTags?: Array<string>;
 | 
				
			||||||
 | 
					    adText?: string;
 | 
				
			||||||
 | 
					    adTextPosition?: 'top right' | 'top left' | 'bottom right' | 'bottom left';
 | 
				
			||||||
 | 
					    adClickable?: boolean;
 | 
				
			||||||
 | 
					    vAlign?: 'top' | 'middle' | 'bottom';
 | 
				
			||||||
 | 
					    nonLinearDuration?: number;
 | 
				
			||||||
 | 
					    size?: '468x60' | '300x250' | '728x90';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					declare interface PreRollAdOptions extends AdOptions {
 | 
				
			||||||
 | 
					    roll: 'preRoll';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					declare interface MidRollAdOptions extends AdOptions {
 | 
				
			||||||
 | 
					    roll: 'midRoll';
 | 
				
			||||||
 | 
					    timer: number | string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					declare interface PostRollAdOptions extends AdOptions {
 | 
				
			||||||
 | 
					    roll: 'postRoll';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					declare interface OnPauseRollAdOptions extends AdOptions {
 | 
				
			||||||
 | 
					    roll: 'onPauseRoll';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					declare interface ModulesOptions {
 | 
				
			||||||
 | 
					    configureHls: (options: any) => any;
 | 
				
			||||||
 | 
					    onBeforeInitHls: (hls: any) => void;
 | 
				
			||||||
 | 
					    onAfterInitHls: (hls: any) => void;
 | 
				
			||||||
 | 
					    configureDash: (options: any) => any;
 | 
				
			||||||
 | 
					    onBeforeInitDash: (dash: any) => void;
 | 
				
			||||||
 | 
					    onAfterInitDash: (dash: any) => void;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					declare interface FluidPlayerOptions {
 | 
				
			||||||
 | 
					    layoutControls: Partial<LayoutControls>;
 | 
				
			||||||
 | 
					    vastOptions: Partial<VastOptions>;
 | 
				
			||||||
 | 
					    modules: Partial<ModulesOptions>;
 | 
				
			||||||
 | 
					    onBeforeXMLHttpRequestOpen?: (request: XMLHttpRequest) => void;
 | 
				
			||||||
 | 
					    onBeforeXMLHttpRequest?: (request: XMLHttpRequest) => void;
 | 
				
			||||||
 | 
					    debug?: boolean;
 | 
				
			||||||
 | 
					    captions: Partial<{
 | 
				
			||||||
 | 
					        play: string;
 | 
				
			||||||
 | 
					        pause: string;
 | 
				
			||||||
 | 
					        mute: string;
 | 
				
			||||||
 | 
					        unmute: string;
 | 
				
			||||||
 | 
					        fullscreen: string;
 | 
				
			||||||
 | 
					        exitFullscreen: string;
 | 
				
			||||||
 | 
					    }>;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										33
									
								
								client/fluid-player/src/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,33 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if ('undefined' === typeof FP_HOMEPAGE) {
 | 
				
			||||||
 | 
					    globalThis.FP_HOMEPAGE = 'https://fluidplayer.com';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if ('undefined' === typeof FP_BUILD_VERSION) {
 | 
				
			||||||
 | 
					    globalThis.FP_BUILD_VERSION = 'v3';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if ('undefined' === typeof FP_ENV) {
 | 
				
			||||||
 | 
					    const isLocalhost = globalThis
 | 
				
			||||||
 | 
					        && globalThis.location
 | 
				
			||||||
 | 
					        && (globalThis.location.hostname === 'localhost'
 | 
				
			||||||
 | 
					            || globalThis.location.hostname === '127.0.0.1'
 | 
				
			||||||
 | 
					            || globalThis.location.hostname === '');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if ('undefined' !== typeof process && process && process.env && process.env.NODE_ENV) {
 | 
				
			||||||
 | 
					        globalThis.FP_ENV = process.env.NODE_ENV;
 | 
				
			||||||
 | 
					    } else if (globalThis && !isLocalhost) {
 | 
				
			||||||
 | 
					        globalThis.FP_ENV = 'production';
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        globalThis.FP_ENV = 'development';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if ('undefined' === typeof FP_DEBUG) {
 | 
				
			||||||
 | 
					    globalThis.FP_DEBUG = false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import './polyfills';
 | 
				
			||||||
 | 
					import fluidPlayerInitializer from './fluidplayer.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default fluidPlayerInitializer;
 | 
				
			||||||
							
								
								
									
										1751
									
								
								client/fluid-player/src/modules/adsupport.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										343
									
								
								client/fluid-player/src/modules/cardboard.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,343 @@
 | 
				
			||||||
 | 
					export default function (playerInstance, options) {
 | 
				
			||||||
 | 
					    playerInstance.createCardboardJoystickButton = (identity) => {
 | 
				
			||||||
 | 
					        const vrJoystickPanel = playerInstance.domRef.wrapper.querySelector('.fluid_vr_joystick_panel');
 | 
				
			||||||
 | 
					        const joystickButton = document.createElement('div');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        joystickButton.className = 'fluid_vr_button fluid_vr_joystick_' + identity;
 | 
				
			||||||
 | 
					        vrJoystickPanel.appendChild(joystickButton);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return joystickButton;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.cardboardRotateLeftRight = (param /* 0 - right, 1 - left */) => {
 | 
				
			||||||
 | 
					        const go = playerInstance.vrROTATION_POSITION;
 | 
				
			||||||
 | 
					        const back = -playerInstance.vrROTATION_POSITION;
 | 
				
			||||||
 | 
					        const pos = param < 1 ? go : back;
 | 
				
			||||||
 | 
					        const easing = {val: pos};
 | 
				
			||||||
 | 
					        const tween = new TWEEN.Tween(easing)
 | 
				
			||||||
 | 
					            .to({val: 0}, playerInstance.vrROTATION_SPEED)
 | 
				
			||||||
 | 
					            .easing(TWEEN.Easing.Quadratic.InOut)
 | 
				
			||||||
 | 
					            .onUpdate(function () {
 | 
				
			||||||
 | 
					                playerInstance.vrViewer.OrbitControls.rotateLeft(easing.val)
 | 
				
			||||||
 | 
					            }).start();
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.cardboardRotateUpDown = (param /* 0 - down, 1- up */) => {
 | 
				
			||||||
 | 
					        const go = playerInstance.vrROTATION_POSITION;
 | 
				
			||||||
 | 
					        const back = -playerInstance.vrROTATION_POSITION;
 | 
				
			||||||
 | 
					        const pos = param < 1 ? go : back;
 | 
				
			||||||
 | 
					        const easing = {val: pos};
 | 
				
			||||||
 | 
					        const tween = new TWEEN.Tween(easing)
 | 
				
			||||||
 | 
					            .to({val: 0}, playerInstance.vrROTATION_SPEED)
 | 
				
			||||||
 | 
					            .easing(TWEEN.Easing.Quadratic.InOut)
 | 
				
			||||||
 | 
					            .onUpdate(function () {
 | 
				
			||||||
 | 
					                playerInstance.vrViewer.OrbitControls.rotateUp(easing.val)
 | 
				
			||||||
 | 
					            }).start();
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.createCardboardJoystick = () => {
 | 
				
			||||||
 | 
					        const vrContainer = playerInstance.domRef.wrapper.querySelector('.fluid_vr_container');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Create a JoyStick and append to VR container
 | 
				
			||||||
 | 
					        const vrJoystickPanel = document.createElement('div');
 | 
				
			||||||
 | 
					        vrJoystickPanel.className = 'fluid_vr_joystick_panel';
 | 
				
			||||||
 | 
					        vrContainer.appendChild(vrJoystickPanel);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Create Joystick buttons
 | 
				
			||||||
 | 
					        const upButton = playerInstance.createCardboardJoystickButton('up');
 | 
				
			||||||
 | 
					        const leftButton = playerInstance.createCardboardJoystickButton('left');
 | 
				
			||||||
 | 
					        const rightButton = playerInstance.createCardboardJoystickButton('right');
 | 
				
			||||||
 | 
					        const downButton = playerInstance.createCardboardJoystickButton('down');
 | 
				
			||||||
 | 
					        const zoomDefaultButton = playerInstance.createCardboardJoystickButton('zoomdefault');
 | 
				
			||||||
 | 
					        const zoomInButton = playerInstance.createCardboardJoystickButton('zoomin');
 | 
				
			||||||
 | 
					        const zoomOutButton = playerInstance.createCardboardJoystickButton('zoomout');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Camera movement buttons
 | 
				
			||||||
 | 
					        upButton.addEventListener('click', function () {
 | 
				
			||||||
 | 
					            //player.vrViewer.OrbitControls.rotateUp(-0.1);
 | 
				
			||||||
 | 
					            playerInstance.cardboardRotateUpDown(1);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        downButton.addEventListener('click', function () {
 | 
				
			||||||
 | 
					            //player.vrViewer.OrbitControls.rotateUp(0.1);
 | 
				
			||||||
 | 
					            playerInstance.cardboardRotateUpDown(0);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        rightButton.addEventListener('click', function () {
 | 
				
			||||||
 | 
					            //player.vrViewer.OrbitControls.rotateLeft(0.1);
 | 
				
			||||||
 | 
					            playerInstance.cardboardRotateLeftRight(0);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        leftButton.addEventListener('click', function () {
 | 
				
			||||||
 | 
					            //player.vrViewer.OrbitControls.rotateLeft(-0.1);
 | 
				
			||||||
 | 
					            playerInstance.cardboardRotateLeftRight(1);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        zoomDefaultButton.addEventListener('click', function () {
 | 
				
			||||||
 | 
					            playerInstance.vrViewer.camera.fov = 60;
 | 
				
			||||||
 | 
					            playerInstance.vrViewer.camera.updateProjectionMatrix();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Camera Zoom buttons
 | 
				
			||||||
 | 
					        zoomOutButton.addEventListener('click', function () {
 | 
				
			||||||
 | 
					            playerInstance.vrViewer.camera.fov *= 1.1;
 | 
				
			||||||
 | 
					            playerInstance.vrViewer.camera.updateProjectionMatrix();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        zoomInButton.addEventListener('click', function () {
 | 
				
			||||||
 | 
					            playerInstance.vrViewer.camera.fov *= 0.9;
 | 
				
			||||||
 | 
					            playerInstance.vrViewer.camera.updateProjectionMatrix();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.cardBoardResize = () => {
 | 
				
			||||||
 | 
					        playerInstance.domRef.player.removeEventListener('theatreModeOn', handleWindowResize);
 | 
				
			||||||
 | 
					        playerInstance.domRef.player.addEventListener('theatreModeOn', handleWindowResize);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        playerInstance.domRef.player.removeEventListener('theatreModeOff', handleWindowResize);
 | 
				
			||||||
 | 
					        playerInstance.domRef.player.addEventListener('theatreModeOff', handleWindowResize);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function handleWindowResize() {
 | 
				
			||||||
 | 
					        playerInstance.vrViewer.onWindowResize();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.cardBoardSwitchToNormal = () => {
 | 
				
			||||||
 | 
					        const vrJoystickPanel = playerInstance.domRef.wrapper.querySelector('.fluid_vr_joystick_panel');
 | 
				
			||||||
 | 
					        const controlBar = playerInstance.domRef.wrapper.querySelector('.fluid_controls_container')
 | 
				
			||||||
 | 
					        const videoPlayerTag = playerInstance.domRef.player;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        playerInstance.vrViewer.enableEffect(PANOLENS.MODES.NORMAL);
 | 
				
			||||||
 | 
					        playerInstance.vrViewer.onWindowResize();
 | 
				
			||||||
 | 
					        playerInstance.vrMode = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // remove dual control bar
 | 
				
			||||||
 | 
					        const newControlBar = videoPlayerTag.parentNode.getElementsByClassName('fluid_vr2_controls_container')[0];
 | 
				
			||||||
 | 
					        videoPlayerTag.parentNode.removeChild(newControlBar);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (playerInstance.displayOptions.layoutControls.showCardBoardJoystick && vrJoystickPanel) {
 | 
				
			||||||
 | 
					            vrJoystickPanel.style.display = "block";
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        controlBar.classList.remove("fluid_vr_controls_container");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // show volume control bar
 | 
				
			||||||
 | 
					        const volumeContainer = playerInstance.domRef.wrapper.getElementById('.fluid_control_volume_container');
 | 
				
			||||||
 | 
					        volumeContainer.style.display = "block";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // show all ads overlays if any
 | 
				
			||||||
 | 
					        const adCountDownTimerText = playerInstance.domRef.wrapper.querySelector('.ad_countdown');
 | 
				
			||||||
 | 
					        const ctaButton = playerInstance.domRef.wrapper.querySelector('.fluid_ad_cta');
 | 
				
			||||||
 | 
					        const addAdPlayingTextOverlay = playerInstance.domRef.wrapper.querySelector('.fluid_ad_playing');
 | 
				
			||||||
 | 
					        const skipBtn = playerInstance.domRef.wrapper.querySelector('.skip_button');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (adCountDownTimerText) {
 | 
				
			||||||
 | 
					            adCountDownTimerText.style.display = 'block';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (ctaButton) {
 | 
				
			||||||
 | 
					            ctaButton.style.display = 'block';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (addAdPlayingTextOverlay) {
 | 
				
			||||||
 | 
					            addAdPlayingTextOverlay.style.display = 'block';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (skipBtn) {
 | 
				
			||||||
 | 
					            skipBtn.style.display = 'block';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.cardBoardHideDefaultControls = () => {
 | 
				
			||||||
 | 
					        const vrJoystickPanel = playerInstance.domRef.wrapper.querySelector('.fluid_vr_joystick_panel');
 | 
				
			||||||
 | 
					        const initialPlay = playerInstance.domRef.wrapper.querySelector('.fluid_initial_play');
 | 
				
			||||||
 | 
					        const volumeContainer = playerInstance.domRef.wrapper.querySelector('.fluid_control_volume_container');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // hide the joystick in VR mode
 | 
				
			||||||
 | 
					        if (playerInstance.displayOptions.layoutControls.showCardBoardJoystick && vrJoystickPanel) {
 | 
				
			||||||
 | 
					            vrJoystickPanel.style.display = "none";
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // hide big play icon
 | 
				
			||||||
 | 
					        if (initialPlay) {
 | 
				
			||||||
 | 
					            playerInstance.domRef.wrapper.querySelector('.fluid_initial_play').style.display = "none";
 | 
				
			||||||
 | 
					            playerInstance.domRef.wrapper.querySelector('.fluid_initial_play_button_container').style.opacity = "1";
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // hide volume control bar
 | 
				
			||||||
 | 
					        volumeContainer.style.display = "none";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.cardBoardCreateVRControls = () => {
 | 
				
			||||||
 | 
					        const controlBar = playerInstance.domRef.wrapper.querySelector('.fluid_controls_container')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // create and append dual control bar
 | 
				
			||||||
 | 
					        const newControlBar = controlBar.cloneNode(true);
 | 
				
			||||||
 | 
					        newControlBar.removeAttribute('id');
 | 
				
			||||||
 | 
					        newControlBar.querySelectorAll('*').forEach(function (node) {
 | 
				
			||||||
 | 
					            node.removeAttribute('id');
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        newControlBar.classList.add("fluid_vr2_controls_container");
 | 
				
			||||||
 | 
					        playerInstance.domRef.player.parentNode.insertBefore(newControlBar, playerInstance.domRef.player.nextSibling);
 | 
				
			||||||
 | 
					        playerInstance.copyEvents(newControlBar);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.cardBoardSwitchToVR = () => {
 | 
				
			||||||
 | 
					        const controlBar = playerInstance.domRef.wrapper.querySelector('.fluid_controls_container')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        playerInstance.vrViewer.enableEffect(PANOLENS.MODES.CARDBOARD);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        playerInstance.vrViewer.onWindowResize();
 | 
				
			||||||
 | 
					        playerInstance.vrViewer.disableReticleControl();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        playerInstance.vrMode = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        controlBar.classList.add("fluid_vr_controls_container");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        playerInstance.cardBoardHideDefaultControls();
 | 
				
			||||||
 | 
					        playerInstance.cardBoardCreateVRControls();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // hide all ads overlays
 | 
				
			||||||
 | 
					        const adCountDownTimerText = playerInstance.domRef.wrapper.querySelector('.ad_countdown');
 | 
				
			||||||
 | 
					        const ctaButton = playerInstance.domRef.wrapper.querySelector('.fluid_ad_cta');
 | 
				
			||||||
 | 
					        const addAdPlayingTextOverlay = playerInstance.domRef.wrapper.querySelector('.fluid_ad_playing');
 | 
				
			||||||
 | 
					        const skipBtn = playerInstance.domRef.wrapper.querySelector('.skip_button');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (adCountDownTimerText) {
 | 
				
			||||||
 | 
					            adCountDownTimerText.style.display = 'none';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (ctaButton) {
 | 
				
			||||||
 | 
					            ctaButton.style.display = 'none';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (addAdPlayingTextOverlay) {
 | 
				
			||||||
 | 
					            addAdPlayingTextOverlay.style.display = 'none';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (skipBtn) {
 | 
				
			||||||
 | 
					            skipBtn.style.display = 'none';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.cardBoardMoveTimeInfo = () => {
 | 
				
			||||||
 | 
					        const timePlaceholder = playerInstance.domRef.wrapper.querySelector('.fluid_control_duration');
 | 
				
			||||||
 | 
					        const controlBar = playerInstance.domRef.wrapper.querySelector('.fluid_controls_container')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        timePlaceholder.classList.add("cardboard_time");
 | 
				
			||||||
 | 
					        controlBar.appendChild(timePlaceholder);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // override the time display function for this instance
 | 
				
			||||||
 | 
					        playerInstance.controlDurationUpdate = function () {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const currentPlayTime = playerInstance.formatTime(playerInstance.domRef.player.currentTime);
 | 
				
			||||||
 | 
					            const totalTime = playerInstance.formatTime(playerInstance.currentVideoDuration);
 | 
				
			||||||
 | 
					            const timePlaceholder = playerInstance.domRef.player.parentNode.getElementsByClassName('fluid_control_duration');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            let durationText = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (playerInstance.isCurrentlyPlayingAd) {
 | 
				
			||||||
 | 
					                durationText = "<span class='ad_timer_prefix'>AD : </span>" + currentPlayTime + ' / ' + totalTime;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                for (let i = 0; i < timePlaceholder.length; i++) {
 | 
				
			||||||
 | 
					                    timePlaceholder[i].classList.add("ad_timer_prefix");
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                durationText = currentPlayTime + ' / ' + totalTime;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                for (let i = 0; i < timePlaceholder.length; i++) {
 | 
				
			||||||
 | 
					                    timePlaceholder[i].classList.remove("ad_timer_prefix");
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            for (let i = 0; i < timePlaceholder.length; i++) {
 | 
				
			||||||
 | 
					                timePlaceholder[i].innerHTML = durationText;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.cardBoardAlterDefaultControls = () => {
 | 
				
			||||||
 | 
					        playerInstance.cardBoardMoveTimeInfo();
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.createCardboardView = () => {
 | 
				
			||||||
 | 
					        // Create a container for 360degree
 | 
				
			||||||
 | 
					        const vrContainer = document.createElement('div');
 | 
				
			||||||
 | 
					        vrContainer.className = 'fluid_vr_container';
 | 
				
			||||||
 | 
					        playerInstance.domRef.player.parentNode.insertBefore(vrContainer, playerInstance.domRef.player.nextSibling);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // OverRide some conflicting functions from panolens
 | 
				
			||||||
 | 
					        PANOLENS.VideoPanorama.prototype.pauseVideo = function () {
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        PANOLENS.VideoPanorama.prototype.playVideo = function () {
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        playerInstance.vrPanorama = new PANOLENS.VideoPanorama('', {
 | 
				
			||||||
 | 
					            videoElement: playerInstance.domRef.player,
 | 
				
			||||||
 | 
					            autoplay: playerInstance.displayOptions.layoutControls.autoPlay,
 | 
				
			||||||
 | 
					            loop: !!playerInstance.displayOptions.layoutControls.loop
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        playerInstance.vrViewer = new PANOLENS.Viewer({
 | 
				
			||||||
 | 
					            container: vrContainer,
 | 
				
			||||||
 | 
					            controlBar: true,
 | 
				
			||||||
 | 
					            controlButtons: [],
 | 
				
			||||||
 | 
					            enableReticle: false
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        playerInstance.vrViewer.add(playerInstance.vrPanorama);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        playerInstance.vrViewer.enableEffect(PANOLENS.MODES.NORMAL);
 | 
				
			||||||
 | 
					        playerInstance.vrViewer.onWindowResize();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // if Mobile device then enable controls using gyroscope
 | 
				
			||||||
 | 
					        if (playerInstance.getMobileOs().userOs === 'Android' || playerInstance.getMobileOs().userOs === 'iOS') {
 | 
				
			||||||
 | 
					            playerInstance.vrViewer.enableControl(1);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Make Changes for default skin
 | 
				
			||||||
 | 
					        playerInstance.cardBoardAlterDefaultControls();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // resize on toggle theater mode
 | 
				
			||||||
 | 
					        playerInstance.cardBoardResize();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Store initial camera position
 | 
				
			||||||
 | 
					        playerInstance.vrViewer.initialCameraPosition = JSON.parse(JSON.stringify(playerInstance.vrViewer.camera.position));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (playerInstance.displayOptions.layoutControls.showCardBoardJoystick) {
 | 
				
			||||||
 | 
					            if (!(playerInstance.getMobileOs().userOs === 'Android' || playerInstance.getMobileOs().userOs === 'iOS')) {
 | 
				
			||||||
 | 
					                playerInstance.createCardboardJoystick();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            // Disable zoom if showing joystick
 | 
				
			||||||
 | 
					            playerInstance.vrViewer.OrbitControls.noZoom = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        playerInstance.trackEvent(playerInstance.domRef.player.parentNode, 'click', '.fluid_control_cardboard', function () {
 | 
				
			||||||
 | 
					            if (playerInstance.vrMode) {
 | 
				
			||||||
 | 
					                playerInstance.cardBoardSwitchToNormal();
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                playerInstance.cardBoardSwitchToVR();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.createCardboard = () => {
 | 
				
			||||||
 | 
					        if (!playerInstance.displayOptions.layoutControls.showCardBoardView) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        playerInstance.domRef.wrapper.querySelector('.fluid_control_cardboard').style.display = 'inline-block';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!window.PANOLENS) {
 | 
				
			||||||
 | 
					            import(/* webpackChunkName: "panolens" */ 'panolens').then((it) => {
 | 
				
			||||||
 | 
					                window.PANOLENS = it;
 | 
				
			||||||
 | 
					                playerInstance.createCardboardView();
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            playerInstance.createCardboardView();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										385
									
								
								client/fluid-player/src/modules/miniplayer.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,385 @@
 | 
				
			||||||
 | 
					export default function (playerInstance) {
 | 
				
			||||||
 | 
					    // Module constants
 | 
				
			||||||
 | 
					    const MINIMUM_WIDTH = 400; // Pixels
 | 
				
			||||||
 | 
					    const MINIMUM_HEIGHT = 225; // Pixels
 | 
				
			||||||
 | 
					    const MINIMUM_WIDTH_MOBILE = 40; // Percentage
 | 
				
			||||||
 | 
					    const TOGGLE_BY_VISIBILITY_DETECTION_RATE = 1000 / 60; // ms
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const DISABLE_MINI_PLAYER_MOBILE_ANIMATION_CLAMP = 50;
 | 
				
			||||||
 | 
					    const DISABLE_MINI_PLAYER_MOBILE_ANIMATION_DEADZONE = 5;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const DESKTOP_ONLY_MEDIA_QUERY = '(max-width: 768px)';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const FLUID_PLAYER_WRAPPER_CLASS = 'fluid_mini_player_mode';
 | 
				
			||||||
 | 
					    const CLOSE_BUTTON_WRAPPER_CLASS = 'mini-player-close-button-wrapper';
 | 
				
			||||||
 | 
					    const CLOSE_BUTTON_CLASS = 'mini-player-close-button';
 | 
				
			||||||
 | 
					    const PLACEHOLDER_CLASS = 'fluidplayer-miniplayer-player-placeholder'
 | 
				
			||||||
 | 
					    const DISABLE_MINI_PLAYER_MOBILE_CLASS = 'disable-mini-player-mobile';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const LINEAR_CLICKTHROUGH_SELECTOR = '.vast_clickthrough_layer';
 | 
				
			||||||
 | 
					    const NON_LINEAR_SELECTOR = '.fluid_nonLinear_ad img, .fluid_vpaid_nonlinear_slot_iframe';
 | 
				
			||||||
 | 
					    const VPAID_FRAME_SELECTOR = '.fluid_vpaidNonLinear_frame';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const MINI_PLAYER_TOGGLE_EVENT = 'miniPlayerToggle';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Module variables
 | 
				
			||||||
 | 
					    let originalWidth = null;
 | 
				
			||||||
 | 
					    let originalHeight = null;
 | 
				
			||||||
 | 
					    let originalNonLinearWidth = null
 | 
				
			||||||
 | 
					    let originalNonLinearHeight = null;
 | 
				
			||||||
 | 
					    let isSetup = false;
 | 
				
			||||||
 | 
					    /** @type null | Element */
 | 
				
			||||||
 | 
					    let placeholderElement = null;
 | 
				
			||||||
 | 
					    let isMobile = false;
 | 
				
			||||||
 | 
					    /** @type boolean */
 | 
				
			||||||
 | 
					    let toggleByVisibilityControl = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Toggles the MiniPlayer given that it's enabled. Resets all other display modes.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param {'on'|'off'} [forceToggle]
 | 
				
			||||||
 | 
					     * @param {boolean} manualToggle
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    function toggleMiniPlayer(forceToggle, manualToggle = false) {
 | 
				
			||||||
 | 
					        playerInstance.debugMessage(`[MiniPlayer] Toggling MiniPlayer, forceToggle: ${forceToggle}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const miniPlayerOptions = playerInstance.displayOptions.layoutControls.miniPlayer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!miniPlayerOptions.enabled) {
 | 
				
			||||||
 | 
					            playerInstance.debugMessage(`[MiniPlayer] Prevent toggle MiniPlayer, it's currently disabled`);
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if ((forceToggle === 'on' && playerInstance.miniPlayerToggledOn) || (forceToggle === 'off' && !playerInstance.miniPlayerToggledOn)) {
 | 
				
			||||||
 | 
					            playerInstance.debugMessage(`[MiniPlayer] Can't force toggle Mini Player to it's same state`);
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (manualToggle) {
 | 
				
			||||||
 | 
					            toggleScreenDetection();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (window.matchMedia(DESKTOP_ONLY_MEDIA_QUERY).matches) {
 | 
				
			||||||
 | 
					            isMobile = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Important as the player can be in full screen or theater mode
 | 
				
			||||||
 | 
					        playerInstance.resetDisplayMode('miniPlayer');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!isSetup) {
 | 
				
			||||||
 | 
					            // Setups JIT to avoid extra processing
 | 
				
			||||||
 | 
					            setupMiniPlayer();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (forceToggle === 'off' || playerInstance.miniPlayerToggledOn) {
 | 
				
			||||||
 | 
					            toggleMiniPlayerOff();
 | 
				
			||||||
 | 
					        } else if (forceToggle === 'on' || !playerInstance.miniPlayerToggledOn) {
 | 
				
			||||||
 | 
					            toggleMiniPlayerOn(miniPlayerOptions.width, miniPlayerOptions.height, miniPlayerOptions.widthMobile, miniPlayerOptions.position);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Setups custom Mini Player DOM
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    function setupMiniPlayer() {
 | 
				
			||||||
 | 
					        const hasCloseButton = Boolean(playerInstance.domRef.player.parentNode.querySelector(`.${CLOSE_BUTTON_CLASS}`));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!hasCloseButton) {
 | 
				
			||||||
 | 
					            const closeButtonWrapper = document.createElement('div');
 | 
				
			||||||
 | 
					            closeButtonWrapper.classList.add(CLOSE_BUTTON_WRAPPER_CLASS);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const closeButton = document.createElement('span');
 | 
				
			||||||
 | 
					            closeButton.classList.add(CLOSE_BUTTON_CLASS);
 | 
				
			||||||
 | 
					            closeButton.addEventListener('click', () => {
 | 
				
			||||||
 | 
					                toggleMiniPlayer('off', true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (!playerInstance.domRef.player.paused) {
 | 
				
			||||||
 | 
					                    playerInstance.playPauseToggle();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            closeButtonWrapper.appendChild(closeButton);
 | 
				
			||||||
 | 
					            playerInstance.domRef.player.parentNode.append(closeButtonWrapper);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (isMobile) {
 | 
				
			||||||
 | 
					            setupMobile();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        isSetup = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Toggles the MiniPlayer off and restores previous functionality to player
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    function toggleMiniPlayerOff() {
 | 
				
			||||||
 | 
					        const videoWrapper = playerInstance.domRef.wrapper;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        removePlayerPlaceholder();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        videoWrapper.classList.remove(FLUID_PLAYER_WRAPPER_CLASS);
 | 
				
			||||||
 | 
					        videoWrapper.style.width = `${originalWidth}px`;
 | 
				
			||||||
 | 
					        videoWrapper.style.height = `${originalHeight}px`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        originalWidth = null;
 | 
				
			||||||
 | 
					        originalHeight = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        adaptNonLinearSize();
 | 
				
			||||||
 | 
					        adaptLinearSize();
 | 
				
			||||||
 | 
					        playerInstance.miniPlayerToggledOn = false;
 | 
				
			||||||
 | 
					        emitToggleEvent();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Toggles the MiniPlayer on, stores the original size of the player.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param {number} width
 | 
				
			||||||
 | 
					     * @param {number} height
 | 
				
			||||||
 | 
					     * @param {number} mobileWidth
 | 
				
			||||||
 | 
					     * @param {'top left'|'top right'|'bottom left'|'bottom right'} position
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    function toggleMiniPlayerOn(width, height, mobileWidth, position) {
 | 
				
			||||||
 | 
					        const videoWrapper = playerInstance.domRef.wrapper;
 | 
				
			||||||
 | 
					        const targetWidth = width > MINIMUM_WIDTH ? width : MINIMUM_WIDTH;
 | 
				
			||||||
 | 
					        const targetHeight = height > MINIMUM_HEIGHT ? height : MINIMUM_HEIGHT;
 | 
				
			||||||
 | 
					        const targetMobileWidth = mobileWidth > MINIMUM_WIDTH_MOBILE ? mobileWidth : MINIMUM_WIDTH_MOBILE;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        originalWidth = extractSizeFromElement(videoWrapper, 'width', 'clientWidth');
 | 
				
			||||||
 | 
					        originalHeight = extractSizeFromElement(videoWrapper, 'height', 'clientHeight');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        videoWrapper.classList.add(
 | 
				
			||||||
 | 
					            FLUID_PLAYER_WRAPPER_CLASS,
 | 
				
			||||||
 | 
					            `${FLUID_PLAYER_WRAPPER_CLASS}--${position.trim().replace(/\s/, '-')}`
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!isMobile) {
 | 
				
			||||||
 | 
					            videoWrapper.style.width = `${targetWidth}px`;
 | 
				
			||||||
 | 
					            videoWrapper.style.height = `${targetHeight}px`;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            videoWrapper.style.width = `${targetMobileWidth}vw`;
 | 
				
			||||||
 | 
					            videoWrapper.style.height = `auto`;
 | 
				
			||||||
 | 
					            videoWrapper.style.aspectRatio = `16 / 9`;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        createPlayerPlaceholder(originalWidth, originalHeight);
 | 
				
			||||||
 | 
					        adaptNonLinearSize(targetWidth, targetHeight, targetMobileWidth);
 | 
				
			||||||
 | 
					        adaptLinearSize();
 | 
				
			||||||
 | 
					        playerInstance.miniPlayerToggledOn = true;
 | 
				
			||||||
 | 
					        emitToggleEvent();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Emits event to Fluid Player Event API
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    function emitToggleEvent() {
 | 
				
			||||||
 | 
					        playerInstance.domRef.player.dispatchEvent(
 | 
				
			||||||
 | 
					            new CustomEvent(MINI_PLAYER_TOGGLE_EVENT, { detail: { isToggledOn: playerInstance.miniPlayerToggledOn } })
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Extracts size from an element checking multiple element properties
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param {HTMLElement} element
 | 
				
			||||||
 | 
					     * @param {'width'|'height'|null} styleProperty
 | 
				
			||||||
 | 
					     * @param {'clientWidth'|'clientHeight'|'width'|'height'} htmlProperty
 | 
				
			||||||
 | 
					     * @returns {number}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    function extractSizeFromElement(element, styleProperty, htmlProperty) {
 | 
				
			||||||
 | 
					        if (styleProperty && element.style[styleProperty] && element.style[styleProperty].match('px')) {
 | 
				
			||||||
 | 
					            return parseInt(element.style[styleProperty]);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            return String(element[htmlProperty]).match('px') ? parseInt(element[htmlProperty]) : element[htmlProperty];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Adapts NonLinear size (if present) to fit MiniPlayer view
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param {number} [width]
 | 
				
			||||||
 | 
					     * @param {number} [height]
 | 
				
			||||||
 | 
					     * @param {number} [mobileWidth]
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    function adaptNonLinearSize(width, height, mobileWidth) {
 | 
				
			||||||
 | 
					        /** @type HTMLImageElement|HTMLIFrameElement */
 | 
				
			||||||
 | 
					        const nonLinear = playerInstance.domRef.wrapper.querySelector(NON_LINEAR_SELECTOR);
 | 
				
			||||||
 | 
					        /** @type HTMLElement */
 | 
				
			||||||
 | 
					        const vpaidFrame = playerInstance.domRef.wrapper.querySelector(VPAID_FRAME_SELECTOR);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!nonLinear) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (isMobile) {
 | 
				
			||||||
 | 
					            width = window.innerWidth * mobileWidth / 100; // Transforms vw to px
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const nonLinearWidth = extractSizeFromElement(nonLinear, null, 'width');
 | 
				
			||||||
 | 
					        const nonLinearHeight = extractSizeFromElement(nonLinear, null, 'height');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (originalNonLinearWidth && originalNonLinearHeight) {
 | 
				
			||||||
 | 
					            nonLinear.width = originalNonLinearWidth;
 | 
				
			||||||
 | 
					            nonLinear.height = originalNonLinearHeight;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (vpaidFrame) {
 | 
				
			||||||
 | 
					                vpaidFrame.style.width = `${originalNonLinearWidth}px`;
 | 
				
			||||||
 | 
					                vpaidFrame.style.height = `${originalNonLinearHeight}px`;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            originalNonLinearWidth = originalNonLinearHeight = null;
 | 
				
			||||||
 | 
					        } else if (nonLinearWidth > width || nonLinearHeight > height) {
 | 
				
			||||||
 | 
					            const targetRatio = (width - (isMobile ? 4 : 32)) / nonLinearWidth;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            originalNonLinearWidth = nonLinearWidth;
 | 
				
			||||||
 | 
					            originalNonLinearHeight = nonLinearHeight;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            nonLinear.width = Math.round(nonLinearWidth * targetRatio);
 | 
				
			||||||
 | 
					            nonLinear.height = Math.round(nonLinearHeight * targetRatio);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (vpaidFrame) {
 | 
				
			||||||
 | 
					                vpaidFrame.style.width = `${Math.round(nonLinearWidth * targetRatio)}px`;
 | 
				
			||||||
 | 
					                vpaidFrame.style.height = `${Math.round(nonLinearHeight * targetRatio)}px`;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Adapts Linear size (if present) to fit MiniPlayer view
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    function adaptLinearSize() {
 | 
				
			||||||
 | 
					        const clickTroughLayer = playerInstance.domRef.wrapper.querySelector(LINEAR_CLICKTHROUGH_SELECTOR);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (clickTroughLayer) {
 | 
				
			||||||
 | 
					            clickTroughLayer.style.width = `${playerInstance.domRef.player.offsetWidth}px`;
 | 
				
			||||||
 | 
					            clickTroughLayer.style.height = `${playerInstance.domRef.player.offsetHeight}px`;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Setups mobile disable element
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    function setupMobile() {
 | 
				
			||||||
 | 
					        const disableMiniPlayerMobile = document.createElement('div');
 | 
				
			||||||
 | 
					        let animationAmount = 0;
 | 
				
			||||||
 | 
					        let startTimestamp = 0;
 | 
				
			||||||
 | 
					        let startScreenX = 0;
 | 
				
			||||||
 | 
					        let hasTriggeredAnimation;
 | 
				
			||||||
 | 
					        disableMiniPlayerMobile.classList.add(DISABLE_MINI_PLAYER_MOBILE_CLASS);
 | 
				
			||||||
 | 
					        const closeButton = document.createElement('span');
 | 
				
			||||||
 | 
					        closeButton.classList.add(CLOSE_BUTTON_CLASS);
 | 
				
			||||||
 | 
					        disableMiniPlayerMobile.appendChild(closeButton); 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        disableMiniPlayerMobile.ontouchstart = event => {
 | 
				
			||||||
 | 
					            hasTriggeredAnimation = false;
 | 
				
			||||||
 | 
					            startTimestamp = event.timeStamp;
 | 
				
			||||||
 | 
					            startScreenX = event.changedTouches[0].screenX;
 | 
				
			||||||
 | 
					            event.preventDefault();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        disableMiniPlayerMobile.ontouchmove = event => {
 | 
				
			||||||
 | 
					            animationAmount = Math.min(
 | 
				
			||||||
 | 
					                Math.max(
 | 
				
			||||||
 | 
					                    startScreenX - event.changedTouches[0].screenX,
 | 
				
			||||||
 | 
					                    DISABLE_MINI_PLAYER_MOBILE_ANIMATION_CLAMP * -1),
 | 
				
			||||||
 | 
					                DISABLE_MINI_PLAYER_MOBILE_ANIMATION_CLAMP
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (Math.abs(animationAmount) > DISABLE_MINI_PLAYER_MOBILE_ANIMATION_DEADZONE) {
 | 
				
			||||||
 | 
					                // Moves the element the same amount as the touch event moved
 | 
				
			||||||
 | 
					                playerInstance.domRef.wrapper.style.transform = `translateX(${animationAmount * -1}px)`;
 | 
				
			||||||
 | 
					                hasTriggeredAnimation = true;
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                playerInstance.domRef.wrapper.style.transform = `translateX(0px)`
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        disableMiniPlayerMobile.ontouchend = event => {
 | 
				
			||||||
 | 
					            if (Math.abs(animationAmount) > DISABLE_MINI_PLAYER_MOBILE_ANIMATION_DEADZONE) {
 | 
				
			||||||
 | 
					                // Scroll X behaviour - Disable mini player and pauses video
 | 
				
			||||||
 | 
					                toggleMiniPlayer('off', true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (!playerInstance.domRef.player.paused) {
 | 
				
			||||||
 | 
					                    playerInstance.playPauseToggle();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                event.preventDefault();
 | 
				
			||||||
 | 
					            } else if (!hasTriggeredAnimation) {
 | 
				
			||||||
 | 
					                // Tap behaviour - Disable mini player and moves screen to video
 | 
				
			||||||
 | 
					                toggleMiniPlayer('off', true);
 | 
				
			||||||
 | 
					                setTimeout(() => {
 | 
				
			||||||
 | 
					                    playerInstance.domRef.wrapper.scrollIntoView({
 | 
				
			||||||
 | 
					                        behavior: 'smooth',
 | 
				
			||||||
 | 
					                        block: 'center',
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            animationAmount = 0;
 | 
				
			||||||
 | 
					            playerInstance.domRef.wrapper.style.transform = ``;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Fallback for when there is no touch event
 | 
				
			||||||
 | 
					        disableMiniPlayerMobile.onmouseup = () => toggleMiniPlayer('off', true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        playerInstance.domRef.wrapper.insertBefore(disableMiniPlayerMobile, playerInstance.domRef.player.nextSibling);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Creates a placeholder element in place where the video player was
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param {number} placeholderWidth
 | 
				
			||||||
 | 
					     * @param {number} placeholderHeight
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    function createPlayerPlaceholder(placeholderWidth, placeholderHeight) {
 | 
				
			||||||
 | 
					        placeholderElement = document.createElement('div');
 | 
				
			||||||
 | 
					        placeholderElement.classList.add(PLACEHOLDER_CLASS);
 | 
				
			||||||
 | 
					        placeholderElement.style.height = `${placeholderHeight}px`;
 | 
				
			||||||
 | 
					        placeholderElement.style.width = `${placeholderWidth}px`;
 | 
				
			||||||
 | 
					        placeholderElement.innerText = playerInstance.displayOptions.layoutControls.miniPlayer.placeholderText || '';
 | 
				
			||||||
 | 
					        placeholderElement.onclick = () => toggleMiniPlayer('off', true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        playerInstance.domRef.wrapper.parentElement.insertBefore(placeholderElement, playerInstance.domRef.wrapper);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Removes the placeholder that was in place where video player was
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    function removePlayerPlaceholder() {
 | 
				
			||||||
 | 
					        playerInstance.domRef.wrapper.parentElement.removeChild(placeholderElement);
 | 
				
			||||||
 | 
					        placeholderElement = null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Toggles auto toggle for mini player
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    function toggleScreenDetection() {
 | 
				
			||||||
 | 
					        const autoToggle = playerInstance.displayOptions.layoutControls.miniPlayer.autoToggle;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (toggleByVisibilityControl || !autoToggle) {
 | 
				
			||||||
 | 
					            document.removeEventListener('scroll', toggleMiniPlayerByVisibility);
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        toggleByVisibilityControl = true;
 | 
				
			||||||
 | 
					        document.addEventListener('scroll', toggleMiniPlayerByVisibility, { passive: true });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Checks for player visibility and toggles mini player based on it
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    const toggleMiniPlayerByVisibility = playerInstance.throttle(function toggleMiniPlayerByVisibility() {
 | 
				
			||||||
 | 
					        if (playerInstance.domRef.player.paused) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const isPlayerVisible = playerInstance.isElementVisible(playerInstance.domRef.player);
 | 
				
			||||||
 | 
					        const isPlaceholderVisible = playerInstance.isElementVisible(playerInstance.domRef.wrapper.querySelector(`.${PLACEHOLDER_CLASS}`));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!isPlayerVisible && !playerInstance.miniPlayerToggledOn) {
 | 
				
			||||||
 | 
					            toggleMiniPlayer('on');
 | 
				
			||||||
 | 
					        } else if (isPlaceholderVisible && playerInstance.miniPlayerToggledOn) {
 | 
				
			||||||
 | 
					            toggleMiniPlayer('off');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }, TOGGLE_BY_VISIBILITY_DETECTION_RATE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Exposes public module functions
 | 
				
			||||||
 | 
					    playerInstance.toggleMiniPlayer = toggleMiniPlayer;
 | 
				
			||||||
 | 
					    playerInstance.toggleMiniPlayerScreenDetection = toggleScreenDetection;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										299
									
								
								client/fluid-player/src/modules/streaming.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,299 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Prevent DASH.js from automatically attaching to video sources by default.
 | 
				
			||||||
 | 
					// Whoever thought this is a good idea?!
 | 
				
			||||||
 | 
					if (typeof window !== 'undefined' && !window.dashjs) {
 | 
				
			||||||
 | 
					    window.dashjs = {
 | 
				
			||||||
 | 
					        skipAutoCreate: true,
 | 
				
			||||||
 | 
					        isDefaultSubject: true
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function (playerInstance, options) {
 | 
				
			||||||
 | 
					    playerInstance.initialiseStreamers = () => {
 | 
				
			||||||
 | 
					        playerInstance.detachStreamers();
 | 
				
			||||||
 | 
					        switch (playerInstance.displayOptions.layoutControls.mediaType) {
 | 
				
			||||||
 | 
					            case 'application/dash+xml': // MPEG-DASH
 | 
				
			||||||
 | 
					                if (!playerInstance.dashScriptLoaded && (!window.dashjs || window.dashjs.isDefaultSubject)) {
 | 
				
			||||||
 | 
					                    playerInstance.dashScriptLoaded = true;
 | 
				
			||||||
 | 
					                    import(/* webpackChunkName: "dashjs" */ 'dashjs').then((it) => {
 | 
				
			||||||
 | 
					                        window.dashjs = it.default;
 | 
				
			||||||
 | 
					                        playerInstance.initialiseDash();
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    playerInstance.initialiseDash();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            case 'application/x-mpegurl': // HLS
 | 
				
			||||||
 | 
					                const { displayOptions, domRef } = playerInstance;
 | 
				
			||||||
 | 
					                const { player } = domRef;
 | 
				
			||||||
 | 
					                const { hls } = displayOptions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Doesn't load hls.js if player can play it natively
 | 
				
			||||||
 | 
					                if (player.canPlayType('application/x-mpegurl') && !hls.overrideNative) {
 | 
				
			||||||
 | 
					                    playerInstance.debugMessage('Native HLS support found, skipping hls.js');
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (!playerInstance.hlsScriptLoaded && !window.Hls) {
 | 
				
			||||||
 | 
					                    playerInstance.hlsScriptLoaded = true;
 | 
				
			||||||
 | 
					                    import(/* webpackChunkName: "hlsjs" */ 'hls.js').then((it) => {
 | 
				
			||||||
 | 
					                        window.Hls = it.default;
 | 
				
			||||||
 | 
					                        playerInstance.initialiseHls();
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    playerInstance.initialiseHls();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.initialiseDash = () => {
 | 
				
			||||||
 | 
					        if (typeof (window.MediaSource || window.WebKitMediaSource) === 'function') {
 | 
				
			||||||
 | 
					            // If false we want to override the autoPlay, as it comes from postRoll
 | 
				
			||||||
 | 
					            const playVideo = !playerInstance.autoplayAfterAd
 | 
				
			||||||
 | 
					                ? playerInstance.autoplayAfterAd
 | 
				
			||||||
 | 
					                : playerInstance.displayOptions.layoutControls.autoPlay;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const defaultOptions = {
 | 
				
			||||||
 | 
					                'debug': {
 | 
				
			||||||
 | 
					                    'logLevel': typeof FP_DEBUG !== 'undefined' && FP_DEBUG === true
 | 
				
			||||||
 | 
					                        ? dashjs.Debug.LOG_LEVEL_DEBUG
 | 
				
			||||||
 | 
					                        : dashjs.Debug.LOG_LEVEL_FATAL
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const dashPlayer = dashjs.MediaPlayer().create();
 | 
				
			||||||
 | 
					            const options = playerInstance.displayOptions.modules.configureDash(defaultOptions);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            dashPlayer.updateSettings(options);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            playerInstance.displayOptions.modules.onBeforeInitDash(dashPlayer);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            dashPlayer.initialize(playerInstance.domRef.player, playerInstance.originalSrc, playVideo);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            dashPlayer.on('streamInitializing', () => {
 | 
				
			||||||
 | 
					                playerInstance.toggleLoader(true);
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            dashPlayer.on('canPlay', () => {
 | 
				
			||||||
 | 
					                playerInstance.toggleLoader(false);
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            dashPlayer.on('playbackPlaying', () => {
 | 
				
			||||||
 | 
					                playerInstance.toggleLoader(false);
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            playerInstance.displayOptions.modules.onAfterInitDash(dashPlayer);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            playerInstance.dashPlayer = dashPlayer;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            playerInstance.nextSource();
 | 
				
			||||||
 | 
					            console.log('[FP_WARNING] Media type not supported by this browser using DASH.js. (application/dash+xml)');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.initialiseHls = () => {
 | 
				
			||||||
 | 
					        if (typeof Hls !== 'undefined' && Hls.isSupported()) {
 | 
				
			||||||
 | 
					            playerInstance.debugMessage('Initializing hls.js');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const defaultOptions = {
 | 
				
			||||||
 | 
					                debug: typeof FP_DEBUG !== 'undefined' && FP_DEBUG === true,
 | 
				
			||||||
 | 
					                startPosition: 0,
 | 
				
			||||||
 | 
					                p2pConfig: {
 | 
				
			||||||
 | 
					                    logLevel: false,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                enableWebVTT: false,
 | 
				
			||||||
 | 
					                enableCEA708Captions: false,
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const options = playerInstance.displayOptions.modules.configureHls(defaultOptions);
 | 
				
			||||||
 | 
					            const hls = new Hls(options);
 | 
				
			||||||
 | 
					            playerInstance.displayOptions.modules.onBeforeInitHls(hls);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            hls.attachMedia(playerInstance.domRef.player);
 | 
				
			||||||
 | 
					            hls.loadSource(playerInstance.originalSrc);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            playerInstance.displayOptions.modules.onAfterInitHls(hls);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            playerInstance.hlsPlayer = hls;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!playerInstance.firstPlayLaunched && playerInstance.displayOptions.layoutControls.autoPlay) {
 | 
				
			||||||
 | 
					                playerInstance.domRef.player.play();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            playerInstance.createHLSVideoSourceSwitch();
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            playerInstance.nextSource();
 | 
				
			||||||
 | 
					            console.log('[FP_WARNING] Media type not supported by this browser using HLS.js. (application/x-mpegURL)');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.createHLSVideoSourceSwitch = () => {
 | 
				
			||||||
 | 
					        playerInstance.hlsPlayer.on(Hls.Events.MANIFEST_PARSED, function () {
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                const levels = createHLSLevels();
 | 
				
			||||||
 | 
					                const sortedLevels = sortLevels(levels);
 | 
				
			||||||
 | 
					                playerInstance.videoSources = sortedLevels;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // <=2 because of the added auto function
 | 
				
			||||||
 | 
					                if (sortedLevels.length <= 2) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const sourceChangeButton = playerInstance.domRef.wrapper.querySelector('.fluid_control_video_source');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                toggleSourceChangeButtonVisibility(sortedLevels, sourceChangeButton);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const sourceChangeList = createSourceChangeList(sortedLevels);
 | 
				
			||||||
 | 
					                attachSourceChangeList(sourceChangeButton, sourceChangeList);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Set initial level based on persisted quality or default to auto
 | 
				
			||||||
 | 
					                setInitialLevel(sortedLevels);
 | 
				
			||||||
 | 
					            } catch (err) {
 | 
				
			||||||
 | 
					                console.error(err);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function createHLSLevels() {
 | 
				
			||||||
 | 
					        const HLSLevels = playerInstance.hlsPlayer.levels
 | 
				
			||||||
 | 
					            .map((level, index) => ({
 | 
				
			||||||
 | 
					                id: index,
 | 
				
			||||||
 | 
					                title: String(level.width),
 | 
				
			||||||
 | 
					                isHD: level.videoRange === 'HDR',
 | 
				
			||||||
 | 
					                bitrate: level.bitrate
 | 
				
			||||||
 | 
					            }));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const autoLevel = {
 | 
				
			||||||
 | 
					            id: -1,
 | 
				
			||||||
 | 
					            title: 'auto',
 | 
				
			||||||
 | 
					            isHD: false,
 | 
				
			||||||
 | 
					            bitrate: 0
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return [...HLSLevels, autoLevel];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function toggleSourceChangeButtonVisibility(levels, sourceChangeButton) {
 | 
				
			||||||
 | 
					        if (levels.length > 1) {
 | 
				
			||||||
 | 
					            sourceChangeButton.style.display = 'inline-block';
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            sourceChangeButton.style.display = 'none';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function createSourceChangeList(levels) {
 | 
				
			||||||
 | 
					        const sourceChangeList = document.createElement('div');
 | 
				
			||||||
 | 
					        sourceChangeList.className = 'fluid_video_sources_list';
 | 
				
			||||||
 | 
					        sourceChangeList.style.display = 'none';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        levels.forEach(level => {
 | 
				
			||||||
 | 
					            const sourceChangeDiv = createSourceChangeItem(level);
 | 
				
			||||||
 | 
					            sourceChangeList.appendChild(sourceChangeDiv);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return sourceChangeList;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function createSourceChangeItem(level) {
 | 
				
			||||||
 | 
					        const sourceSelectedClass = getSourceSelectedClass(level);
 | 
				
			||||||
 | 
					        const hdIndicator = level.isHD ? `<sup style="color:${playerInstance.displayOptions.layoutControls.primaryColor}" class="fp_hd_source"></sup>` : '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const sourceChangeDiv = document.createElement('div');
 | 
				
			||||||
 | 
					        sourceChangeDiv.className = `fluid_video_source_list_item js-source_${level.title}`;
 | 
				
			||||||
 | 
					        sourceChangeDiv.innerHTML = `<span class="source_button_icon ${sourceSelectedClass}"></span>${level.title}${hdIndicator}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        sourceChangeDiv.addEventListener('click', event => onSourceChangeClick(event, level));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return sourceChangeDiv;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function getSourceSelectedClass(level) {
 | 
				
			||||||
 | 
					        const matchingLevels = playerInstance.videoSources.filter(source => source.title === playerInstance.fluidStorage.fluidQuality);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // If there are multiple matching levels, use the first one
 | 
				
			||||||
 | 
					        if (matchingLevels.length > 1) {
 | 
				
			||||||
 | 
					            if (matchingLevels[0].id === level.id) {
 | 
				
			||||||
 | 
					                return "source_selected";
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else if (matchingLevels.length === 1) {
 | 
				
			||||||
 | 
					            return matchingLevels[0].id === level.id ? "source_selected" : "";
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Fallback to auto selection if no persistent level exists
 | 
				
			||||||
 | 
					        if (!matchingLevels.length && level.title === 'auto') {
 | 
				
			||||||
 | 
					            return "source_selected";
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return "";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function onSourceChangeClick(event, selectedLevel) {
 | 
				
			||||||
 | 
					        event.stopPropagation();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        setPlayerDimensions();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const videoChangedTo = event.currentTarget;
 | 
				
			||||||
 | 
					        clearSourceSelectedIcons();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        videoChangedTo.firstChild.classList.add('source_selected');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        playerInstance.videoSources.forEach(source => {
 | 
				
			||||||
 | 
					            if (source.title === videoChangedTo.innerText.replace(/(\r\n\t|\n|\r\t)/gm, '')) {
 | 
				
			||||||
 | 
					                playerInstance.hlsPlayer.currentLevel = selectedLevel.id;
 | 
				
			||||||
 | 
					                playerInstance.fluidStorage.fluidQuality = selectedLevel.title;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        playerInstance.openCloseVideoSourceSwitch();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // While changing source the player size can flash, we want to set the pixel dimensions then back to 100% afterwards
 | 
				
			||||||
 | 
					    function setPlayerDimensions() {
 | 
				
			||||||
 | 
					        playerInstance.domRef.player.style.width = `${playerInstance.domRef.player.clientWidth}px`;
 | 
				
			||||||
 | 
					        playerInstance.domRef.player.style.height = `${playerInstance.domRef.player.clientHeight}px`;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function clearSourceSelectedIcons() {
 | 
				
			||||||
 | 
					        const sourceIcons = playerInstance.domRef.wrapper.getElementsByClassName('source_button_icon');
 | 
				
			||||||
 | 
					        Array.from(sourceIcons).forEach(icon => icon.classList.remove('source_selected'));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function attachSourceChangeList(sourceChangeButton, sourceChangeList) {
 | 
				
			||||||
 | 
					        sourceChangeButton.appendChild(sourceChangeList);
 | 
				
			||||||
 | 
					        sourceChangeButton.removeEventListener('click', playerInstance.openCloseVideoSourceSwitch);
 | 
				
			||||||
 | 
					        sourceChangeButton.addEventListener('click', playerInstance.openCloseVideoSourceSwitch);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function setInitialLevel(levels) {
 | 
				
			||||||
 | 
					        // Check if a persistency level exists and set the current level accordingly
 | 
				
			||||||
 | 
					        const persistedLevel = levels.find(level => level.title === playerInstance.fluidStorage.fluidQuality);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (persistedLevel) {
 | 
				
			||||||
 | 
					            playerInstance.hlsPlayer.currentLevel = persistedLevel.id;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            // Default to 'auto' if no persisted level is found
 | 
				
			||||||
 | 
					            const autoLevel = levels.find(level => level.title === 'auto');
 | 
				
			||||||
 | 
					            playerInstance.hlsPlayer.currentLevel = autoLevel.id;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function sortLevels(levels) {
 | 
				
			||||||
 | 
					        return [...levels].sort((a, b) => {
 | 
				
			||||||
 | 
					            // First sort by width in descending order
 | 
				
			||||||
 | 
					            if (b.width !== a.width) {
 | 
				
			||||||
 | 
					                return b.width - a.width;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            // If width is the same, sort by bitrate in descending order
 | 
				
			||||||
 | 
					            return b.bitrate - a.bitrate;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.detachStreamers = () => {
 | 
				
			||||||
 | 
					        if (playerInstance.dashPlayer) {
 | 
				
			||||||
 | 
					            playerInstance.dashPlayer.reset();
 | 
				
			||||||
 | 
					            playerInstance.dashPlayer = false;
 | 
				
			||||||
 | 
					        } else if (playerInstance.hlsPlayer) {
 | 
				
			||||||
 | 
					            playerInstance.hlsPlayer.detachMedia();
 | 
				
			||||||
 | 
					            playerInstance.hlsPlayer = false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										225
									
								
								client/fluid-player/src/modules/subtitles.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,225 @@
 | 
				
			||||||
 | 
					export default function (playerInstance, options) {
 | 
				
			||||||
 | 
					    playerInstance.subtitleFetchParse = (subtitleItem) => {
 | 
				
			||||||
 | 
					        playerInstance.sendRequest(
 | 
				
			||||||
 | 
					            subtitleItem.url,
 | 
				
			||||||
 | 
					            true,
 | 
				
			||||||
 | 
					            playerInstance.displayOptions.vastOptions.vastTimeout,
 | 
				
			||||||
 | 
					            function () {
 | 
				
			||||||
 | 
					                const convertVttRawData = function (vttRawData) {
 | 
				
			||||||
 | 
					                    if (!(
 | 
				
			||||||
 | 
					                        (typeof vttRawData.cues !== 'undefined') &&
 | 
				
			||||||
 | 
					                        (vttRawData.cues.length)
 | 
				
			||||||
 | 
					                    )) {
 | 
				
			||||||
 | 
					                        return [];
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    const result = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    for (let i = 0; i < vttRawData.cues.length; i++) {
 | 
				
			||||||
 | 
					                        let tempThumbnailData = vttRawData.cues[i].text.split('#');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        result.push({
 | 
				
			||||||
 | 
					                            startTime: vttRawData.cues[i].startTime,
 | 
				
			||||||
 | 
					                            endTime: vttRawData.cues[i].endTime,
 | 
				
			||||||
 | 
					                            text: vttRawData.cues[i].text,
 | 
				
			||||||
 | 
					                            cue: vttRawData.cues[i]
 | 
				
			||||||
 | 
					                        })
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    return result;
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const xmlHttpReq = this;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if ((xmlHttpReq.readyState === 4) && (xmlHttpReq.status !== 200)) {
 | 
				
			||||||
 | 
					                    //The response returned an error.
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (!((xmlHttpReq.readyState === 4) && (xmlHttpReq.status === 200))) {
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const textResponse = xmlHttpReq.responseText;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const parser = new WebVTT.Parser(window, WebVTT.StringDecoder());
 | 
				
			||||||
 | 
					                const cues = [];
 | 
				
			||||||
 | 
					                const regions = []; // TODO: unused?
 | 
				
			||||||
 | 
					                parser.oncue = function (cue) {
 | 
				
			||||||
 | 
					                    cues.push(cue);
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					                parser.onregion = function (region) {
 | 
				
			||||||
 | 
					                    regions.push(region);
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					                parser.parse(textResponse);
 | 
				
			||||||
 | 
					                parser.flush();
 | 
				
			||||||
 | 
					                playerInstance.subtitlesData = cues;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.createSubtitlesSwitch = () => {
 | 
				
			||||||
 | 
					        const subtitlesOff = 'OFF';
 | 
				
			||||||
 | 
					        playerInstance.subtitlesData = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!playerInstance.displayOptions.layoutControls.subtitlesEnabled) {
 | 
				
			||||||
 | 
					            // No other video subtitles
 | 
				
			||||||
 | 
					            playerInstance.domRef.wrapper.querySelector('.fluid_control_subtitles').style.display = 'none';
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const tracks = [];
 | 
				
			||||||
 | 
					        tracks.push({'label': subtitlesOff, 'url': 'na', 'lang': subtitlesOff});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const tracksList = playerInstance.domRef.player.querySelectorAll('track');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [].forEach.call(tracksList, function (track) {
 | 
				
			||||||
 | 
					            if (track.kind === 'metadata' && track.src) {
 | 
				
			||||||
 | 
					                tracks.push({'label': track.label, 'url': track.src, 'lang': track.srclang, 'default': track.default});
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        playerInstance.subtitlesTracks = tracks;
 | 
				
			||||||
 | 
					        const subtitlesChangeButton = playerInstance.domRef.wrapper.querySelector('.fluid_control_subtitles');
 | 
				
			||||||
 | 
					        subtitlesChangeButton.style.display = 'inline-block';
 | 
				
			||||||
 | 
					        let appendSubtitleChange = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const subtitlesChangeList = document.createElement('div');
 | 
				
			||||||
 | 
					        subtitlesChangeList.className = 'fluid_subtitles_list';
 | 
				
			||||||
 | 
					        subtitlesChangeList.style.display = 'none';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let hasSelectedSubtitle = false;
 | 
				
			||||||
 | 
					        const hasDefault = !!playerInstance.subtitlesTracks.find(track => track.default);
 | 
				
			||||||
 | 
					        playerInstance.subtitlesTracks.forEach(function (subtitle) {
 | 
				
			||||||
 | 
					            let subtitleSelected = ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const subtitlesOnByDefault = playerInstance.displayOptions.layoutControls.subtitlesOnByDefault;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!hasSelectedSubtitle && (subtitlesOnByDefault && subtitle.default ||
 | 
				
			||||||
 | 
					                (!hasDefault && subtitle.label !== subtitlesOff) ||
 | 
				
			||||||
 | 
					                playerInstance.subtitlesTracks.length === 1) ||
 | 
				
			||||||
 | 
					                !subtitlesOnByDefault && subtitle.label === subtitlesOff
 | 
				
			||||||
 | 
					            ) {
 | 
				
			||||||
 | 
					                subtitleSelected = 'subtitle_selected';
 | 
				
			||||||
 | 
					                playerInstance.subtitleFetchParse(subtitle);
 | 
				
			||||||
 | 
					                hasSelectedSubtitle = true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const subtitlesChangeDiv = document.createElement('div');
 | 
				
			||||||
 | 
					            subtitlesChangeDiv.className = 'fluid_subtitle_list_item';
 | 
				
			||||||
 | 
					            subtitlesChangeDiv.innerHTML = '<span class="subtitle_button_icon ' + subtitleSelected + '"></span>' + subtitle.label;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            subtitlesChangeDiv.addEventListener('click', function (event) {
 | 
				
			||||||
 | 
					                event.stopPropagation();
 | 
				
			||||||
 | 
					                const subtitleChangedTo = this;
 | 
				
			||||||
 | 
					                const subtitleIcons = playerInstance.domRef.wrapper.getElementsByClassName('subtitle_button_icon');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                for (let i = 0; i < subtitleIcons.length; i++) {
 | 
				
			||||||
 | 
					                    subtitleIcons[i].className = subtitleIcons[i].className.replace("subtitle_selected", "");
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                subtitleChangedTo.firstChild.className += ' subtitle_selected';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                playerInstance.subtitlesTracks.forEach(function (subtitle) {
 | 
				
			||||||
 | 
					                    if (subtitle.label === subtitleChangedTo.innerText.replace(/(\r\n\t|\n|\r\t)/gm, "")) {
 | 
				
			||||||
 | 
					                        if (subtitle.label === subtitlesOff) {
 | 
				
			||||||
 | 
					                            playerInstance.subtitlesData = [];
 | 
				
			||||||
 | 
					                        } else {
 | 
				
			||||||
 | 
					                            playerInstance.subtitleFetchParse(subtitle);
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					                playerInstance.openCloseSubtitlesSwitch();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            subtitlesChangeList.appendChild(subtitlesChangeDiv);
 | 
				
			||||||
 | 
					            appendSubtitleChange = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (appendSubtitleChange) {
 | 
				
			||||||
 | 
					            subtitlesChangeButton.appendChild(subtitlesChangeList);
 | 
				
			||||||
 | 
					            subtitlesChangeButton.removeEventListener('click', handleSubtitlesChange);
 | 
				
			||||||
 | 
					            subtitlesChangeButton.addEventListener('click', handleSubtitlesChange);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            // Didn't give any subtitle options
 | 
				
			||||||
 | 
					            playerInstance.domRef.wrapper.querySelector('.fluid_control_subtitles').style.display = 'none';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        playerInstance.domRef.player.removeEventListener('timeupdate', videoPlayerSubtitlesUpdate);
 | 
				
			||||||
 | 
					        playerInstance.domRef.player.addEventListener('timeupdate', videoPlayerSubtitlesUpdate);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function handleSubtitlesChange() {
 | 
				
			||||||
 | 
					        playerInstance.openCloseSubtitlesSwitch();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    //attach subtitles to show based on time
 | 
				
			||||||
 | 
					    //this function is for rendering of subtitles when content is playing
 | 
				
			||||||
 | 
					    function videoPlayerSubtitlesUpdate() {
 | 
				
			||||||
 | 
					        playerInstance.renderSubtitles();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.renderSubtitles = () => {
 | 
				
			||||||
 | 
					        const videoPlayer = playerInstance.domRef.player;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        //if content is playing then no subtitles
 | 
				
			||||||
 | 
					        let currentTime = Math.floor(videoPlayer.currentTime);
 | 
				
			||||||
 | 
					        let subtitlesAvailable = false;
 | 
				
			||||||
 | 
					        let subtitlesContainer = playerInstance.domRef.wrapper.querySelector('.fluid_subtitles_container');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (playerInstance.isCurrentlyPlayingAd) {
 | 
				
			||||||
 | 
					            subtitlesContainer.innerHTML = '';
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (let i = 0; i < playerInstance.subtitlesData.length; i++) {
 | 
				
			||||||
 | 
					            if (currentTime >= (playerInstance.subtitlesData[i].startTime) && currentTime <= (playerInstance.subtitlesData[i].endTime)) {
 | 
				
			||||||
 | 
					                subtitlesContainer.innerHTML = '';
 | 
				
			||||||
 | 
					                subtitlesContainer.appendChild(WebVTT.convertCueToDOMTree(window, playerInstance.subtitlesData[i].text));
 | 
				
			||||||
 | 
					                subtitlesAvailable = true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!subtitlesAvailable) {
 | 
				
			||||||
 | 
					            subtitlesContainer.innerHTML = '';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.openCloseSubtitlesSwitch = () => {
 | 
				
			||||||
 | 
					        const subtitleChangeList = playerInstance.domRef.wrapper.querySelector('.fluid_subtitles_list');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (playerInstance.isCurrentlyPlayingAd) {
 | 
				
			||||||
 | 
					            subtitleChangeList.style.display = 'none';
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (subtitleChangeList.style.display === 'none') {
 | 
				
			||||||
 | 
					            subtitleChangeList.style.display = 'block';
 | 
				
			||||||
 | 
					            const mouseOut = function (event) {
 | 
				
			||||||
 | 
					                subtitleChangeList.removeEventListener('mouseleave', mouseOut);
 | 
				
			||||||
 | 
					                subtitleChangeList.style.display = 'none';
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					            subtitleChangeList.addEventListener('mouseleave', mouseOut);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            subtitleChangeList.style.display = 'none';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.createSubtitles = () => {
 | 
				
			||||||
 | 
					        const divSubtitlesContainer = document.createElement('div');
 | 
				
			||||||
 | 
					        divSubtitlesContainer.className = 'fluid_subtitles_container';
 | 
				
			||||||
 | 
					        playerInstance.domRef.player.parentNode.insertBefore(divSubtitlesContainer, playerInstance.domRef.player.nextSibling);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!playerInstance.displayOptions.layoutControls.subtitlesEnabled) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        import(/* webpackChunkName: "vttjs" */ 'videojs-vtt.js').then((it) => {
 | 
				
			||||||
 | 
					            window.WebVTT = it.WebVTT || it.default.WebVTT;
 | 
				
			||||||
 | 
					            playerInstance.createSubtitlesSwitch();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										197
									
								
								client/fluid-player/src/modules/suggestedVideos.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,197 @@
 | 
				
			||||||
 | 
					export default function (playerInstance, options) {
 | 
				
			||||||
 | 
					    playerInstance.suggestedVideosGrid = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.generateSuggestedVideoList = () => {
 | 
				
			||||||
 | 
					        const configUrl = playerInstance.displayOptions.suggestedVideos.configUrl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!configUrl) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        playerInstance.sendRequestAsync(configUrl, false, 5000).then(({response}) => {
 | 
				
			||||||
 | 
					            const config = JSON.parse(response);
 | 
				
			||||||
 | 
					            const suggestedVideosGrid = playerInstance.generateSuggestedVideoGrid(config);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            playerInstance.suggestedVideosGrid = suggestedVideosGrid;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        }).catch(err => {
 | 
				
			||||||
 | 
					            console.error('[FP_ERROR] given suggested videos config url is invalid or not found.', err);
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.generateSuggestedVideoGrid = (config) => {
 | 
				
			||||||
 | 
					        const suggestedVideosGrid = document.createElement('div');
 | 
				
			||||||
 | 
					        suggestedVideosGrid.className = 'suggested_tile_grid';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (let i = 0; i < 12; i++) {
 | 
				
			||||||
 | 
					            const videoTile = playerInstance.createVideoTile(config[i]);
 | 
				
			||||||
 | 
					            suggestedVideosGrid.appendChild(videoTile);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return suggestedVideosGrid;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.displaySuggestedVideos = () => {
 | 
				
			||||||
 | 
					        const PlayerDOM = playerInstance.domRef.wrapper;
 | 
				
			||||||
 | 
					        PlayerDOM.appendChild(playerInstance.suggestedVideosGrid);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.clickSuggestedVideo = (sources, subtitles, configUrl) => {
 | 
				
			||||||
 | 
					        playerInstance.toggleLoader(true);
 | 
				
			||||||
 | 
					        playerInstance.hideSuggestedVideos();
 | 
				
			||||||
 | 
					        playerInstance.resetPlayer(sources, subtitles, configUrl);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        playerInstance.generateSuggestedVideoList();
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.resetPlayer = (sources, subtitles, configUrl) => {
 | 
				
			||||||
 | 
					        const videoDOM = playerInstance.domRef.wrapper.querySelector(`#${playerInstance.videoPlayerId}`);
 | 
				
			||||||
 | 
					        videoDOM.innerHTML = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let sourcesHTML = '';
 | 
				
			||||||
 | 
					        if (sources) {
 | 
				
			||||||
 | 
					            sources.forEach(source => {
 | 
				
			||||||
 | 
					                sourcesHTML += `<source src='${source.url}' ${source.hd ? 'data-fluid-hd' : ''} title="${source.resolution}" type='${source.mimeType}'/>`;
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (subtitles) {
 | 
				
			||||||
 | 
					            subtitles.forEach(subtitle => {
 | 
				
			||||||
 | 
					                sourcesHTML += `<track label="${subtitle.label}" kind="metadata" srclang="${subtitle.lang}" src="${subtitle.url}" ${subtitle.default ? 'default' : ''}>`;
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        videoDOM.innerHTML = sourcesHTML;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        playerInstance.removeVideoSourcesListFromDOM();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const videoSourceList = playerInstance.domRef.wrapper.getElementsByClassName('fluid_control_video_source')[0];
 | 
				
			||||||
 | 
					        videoSourceList.innerHTML = '';
 | 
				
			||||||
 | 
					        playerInstance.domRef.player.src = '';
 | 
				
			||||||
 | 
					        playerInstance.domRef.player.removeAttribute('src');
 | 
				
			||||||
 | 
					        playerInstance.setVideoSource(sources[0].url);
 | 
				
			||||||
 | 
					        playerInstance.createVideoSourceSwitch(false);
 | 
				
			||||||
 | 
					        playerInstance.resetSubtitles();
 | 
				
			||||||
 | 
					        playerInstance.setPersistentSettings(true);
 | 
				
			||||||
 | 
					        playerInstance.loadInNewVideo();
 | 
				
			||||||
 | 
					        playerInstance.resetAds();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // set new API config url
 | 
				
			||||||
 | 
					        if (configUrl) {
 | 
				
			||||||
 | 
					            playerInstance.displayOptions.suggestedVideos.configUrl = configUrl;
 | 
				
			||||||
 | 
					            playerInstance.generateSuggestedVideoList();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.createVideoTile = (config) => {
 | 
				
			||||||
 | 
					        const videoTile = document.createElement('div');
 | 
				
			||||||
 | 
					        videoTile.addEventListener('click', function () {
 | 
				
			||||||
 | 
					            playerInstance.clickSuggestedVideo(config.sources, config.subtitles, config.configUrl);
 | 
				
			||||||
 | 
					        }, false);
 | 
				
			||||||
 | 
					        videoTile.className = 'suggested_tile';
 | 
				
			||||||
 | 
					        videoTile.id = 'suggested_tile_' + config.id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        playerInstance.getImageTwoMostProminentColours(config.thumbnailUrl).then(mostProminentColors => {
 | 
				
			||||||
 | 
					            if (mostProminentColors && mostProminentColors.length) {
 | 
				
			||||||
 | 
					                videoTile.style = `background: ${mostProminentColors[0]};`;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const videoImage = document.createElement('img');
 | 
				
			||||||
 | 
					        videoImage.src = config.thumbnailUrl;
 | 
				
			||||||
 | 
					        videoImage.className = 'suggested_tile_image';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const videoTileOverlay = document.createElement('div');
 | 
				
			||||||
 | 
					        videoTileOverlay.className='suggested_tile_overlay';
 | 
				
			||||||
 | 
					        const title = document.createElement('p');
 | 
				
			||||||
 | 
					        title.className = 'suggested_tile_title';
 | 
				
			||||||
 | 
					        title.innerText = config.title;
 | 
				
			||||||
 | 
					        videoTileOverlay.appendChild(title);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        videoTile.appendChild(videoImage);
 | 
				
			||||||
 | 
					        videoTile.appendChild(videoTileOverlay);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return videoTile;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.resetSubtitles = () => {
 | 
				
			||||||
 | 
					        playerInstance.removeSubtitlesListFromDOM();
 | 
				
			||||||
 | 
					        const videoSubtitlesList = playerInstance.domRef.wrapper.getElementsByClassName('fluid_control_subtitles')[0];
 | 
				
			||||||
 | 
					        videoSubtitlesList.innerHTML = '';
 | 
				
			||||||
 | 
					        playerInstance.domRef.player.load();
 | 
				
			||||||
 | 
					        playerInstance.createSubtitles(false);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.loadInNewVideo = () => {
 | 
				
			||||||
 | 
					        playerInstance.displayOptions.layoutControls.mediaType = playerInstance.getCurrentSrcType();
 | 
				
			||||||
 | 
					        playerInstance.initialiseStreamers();
 | 
				
			||||||
 | 
					        playerInstance.domRef.player.currentTime = 0;
 | 
				
			||||||
 | 
					        playerInstance.domRef.player.mainVideoCurrentTime = 0;
 | 
				
			||||||
 | 
					        playerInstance.setBuffering();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.resetAds = () => {
 | 
				
			||||||
 | 
					         // Clear midroll and postroll ads
 | 
				
			||||||
 | 
					         playerInstance.timerPool = {};
 | 
				
			||||||
 | 
					         playerInstance.rollsById = {};
 | 
				
			||||||
 | 
					         playerInstance.adPool = {};
 | 
				
			||||||
 | 
					         playerInstance.adGroupedByRolls = {};
 | 
				
			||||||
 | 
					         playerInstance.onPauseRollAdPods = [];
 | 
				
			||||||
 | 
					         playerInstance.currentOnPauseRollAd = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					         // Reset variables and flags, needed for assigning the different rolls correctly
 | 
				
			||||||
 | 
					         playerInstance.isTimer = false;
 | 
				
			||||||
 | 
					         playerInstance.timer = null;
 | 
				
			||||||
 | 
					         playerInstance.firstPlayLaunched = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					         // Clear preroll ads
 | 
				
			||||||
 | 
					         playerInstance.preRollAdsResolved = false;
 | 
				
			||||||
 | 
					         playerInstance.preRollAdPods = [];
 | 
				
			||||||
 | 
					         playerInstance.preRollAdPodsLength = 0;
 | 
				
			||||||
 | 
					         playerInstance.preRollVastResolved = 0;
 | 
				
			||||||
 | 
					         playerInstance.autoplayAfterAd = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					         // Wait until new selected video is buffered so we can get the video length
 | 
				
			||||||
 | 
					         // This is needed for mid and post rolls to assign the correct time key to their respective triggers
 | 
				
			||||||
 | 
					         const checkMainVideoDuration = () => {
 | 
				
			||||||
 | 
					             if (!isNaN(playerInstance.domRef.player.duration) && playerInstance.domRef.player.duration > 0) {
 | 
				
			||||||
 | 
					                playerInstance.toggleLoader(true);
 | 
				
			||||||
 | 
					                 playerInstance.mainVideoDuration = playerInstance.domRef.player.duration;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                 clearInterval(intervalId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                 // Set up ads
 | 
				
			||||||
 | 
					                 playerInstance.setVastList();
 | 
				
			||||||
 | 
					                 playerInstance.checkForNextAd();
 | 
				
			||||||
 | 
					                 playerInstance.playPauseToggle();
 | 
				
			||||||
 | 
					             }
 | 
				
			||||||
 | 
					         };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					         const intervalId = setInterval(checkMainVideoDuration, 100);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.removeVideoSourcesListFromDOM = () => {
 | 
				
			||||||
 | 
					        const sourcesDOM = playerInstance.domRef.wrapper.getElementsByClassName('fluid_video_source_list_item');
 | 
				
			||||||
 | 
					        for (let i = 0; i < sourcesDOM.length; i++) {
 | 
				
			||||||
 | 
					            sourcesDOM[i].remove();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.removeSubtitlesListFromDOM = () => {
 | 
				
			||||||
 | 
					        const tracksDOM = playerInstance.domRef.wrapper.getElementsByClassName('fluid_subtitle_list_item');
 | 
				
			||||||
 | 
					        for (let i = 0; i < tracksDOM.length; i++) {
 | 
				
			||||||
 | 
					            tracksDOM[i].remove();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.hideSuggestedVideos = () => {
 | 
				
			||||||
 | 
					        const suggestedVideosDOM = playerInstance.domRef.wrapper.getElementsByClassName('suggested_tile_grid')[0];
 | 
				
			||||||
 | 
					        if (suggestedVideosDOM) {
 | 
				
			||||||
 | 
					            suggestedVideosDOM.remove();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.isShowingSuggestedVideos = () => {
 | 
				
			||||||
 | 
					        return !!playerInstance.domRef.wrapper.getElementsByClassName('suggested_tile_grid')[0];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										202
									
								
								client/fluid-player/src/modules/timeline.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,202 @@
 | 
				
			||||||
 | 
					export default function (playerInstance, options) {
 | 
				
			||||||
 | 
					    playerInstance.setupThumbnailPreviewVtt = () => {
 | 
				
			||||||
 | 
					        playerInstance.sendRequest(
 | 
				
			||||||
 | 
					            playerInstance.displayOptions.layoutControls.timelinePreview.file,
 | 
				
			||||||
 | 
					            true,
 | 
				
			||||||
 | 
					            playerInstance.displayOptions.vastOptions.vastTimeout,
 | 
				
			||||||
 | 
					            function () {
 | 
				
			||||||
 | 
					                const convertVttRawData = function (vttRawData) {
 | 
				
			||||||
 | 
					                    if (!(
 | 
				
			||||||
 | 
					                        (typeof vttRawData.cues !== 'undefined') &&
 | 
				
			||||||
 | 
					                        (vttRawData.cues.length)
 | 
				
			||||||
 | 
					                    )) {
 | 
				
			||||||
 | 
					                        return [];
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    const result = [];
 | 
				
			||||||
 | 
					                    let tempThumbnailData = null;
 | 
				
			||||||
 | 
					                    let tempThumbnailCoordinates = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    for (let i = 0; i < vttRawData.cues.length; i++) {
 | 
				
			||||||
 | 
					                        tempThumbnailData = vttRawData.cues[i].text.split('#');
 | 
				
			||||||
 | 
					                        let xCoords = 0, yCoords = 0, wCoords = 122.5, hCoords = 69;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        // .vtt file contains sprite corrdinates
 | 
				
			||||||
 | 
					                        if (
 | 
				
			||||||
 | 
					                            (tempThumbnailData.length === 2) &&
 | 
				
			||||||
 | 
					                            (tempThumbnailData[1].indexOf('xywh=') === 0)
 | 
				
			||||||
 | 
					                        ) {
 | 
				
			||||||
 | 
					                            tempThumbnailCoordinates = tempThumbnailData[1].substring(5);
 | 
				
			||||||
 | 
					                            tempThumbnailCoordinates = tempThumbnailCoordinates.split(',');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            if (tempThumbnailCoordinates.length === 4) {
 | 
				
			||||||
 | 
					                                playerInstance.displayOptions.layoutControls.timelinePreview.spriteImage = true;
 | 
				
			||||||
 | 
					                                xCoords = parseInt(tempThumbnailCoordinates[0]);
 | 
				
			||||||
 | 
					                                yCoords = parseInt(tempThumbnailCoordinates[1]);
 | 
				
			||||||
 | 
					                                wCoords = parseInt(tempThumbnailCoordinates[2]);
 | 
				
			||||||
 | 
					                                hCoords = parseInt(tempThumbnailCoordinates[3]);
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        let imageUrl;
 | 
				
			||||||
 | 
					                        if (playerInstance.displayOptions.layoutControls.timelinePreview.spriteRelativePath
 | 
				
			||||||
 | 
					                            && playerInstance.displayOptions.layoutControls.timelinePreview.file.indexOf('/') !== -1
 | 
				
			||||||
 | 
					                            && (typeof playerInstance.displayOptions.layoutControls.timelinePreview.sprite === 'undefined' || playerInstance.displayOptions.layoutControls.timelinePreview.sprite === '')
 | 
				
			||||||
 | 
					                        ) {
 | 
				
			||||||
 | 
					                            imageUrl = playerInstance.displayOptions.layoutControls.timelinePreview.file.substring(0, playerInstance.displayOptions.layoutControls.timelinePreview.file.lastIndexOf('/'));
 | 
				
			||||||
 | 
					                            imageUrl += '/' + tempThumbnailData[0];
 | 
				
			||||||
 | 
					                        } else {
 | 
				
			||||||
 | 
					                            imageUrl = (playerInstance.displayOptions.layoutControls.timelinePreview.sprite ? playerInstance.displayOptions.layoutControls.timelinePreview.sprite : tempThumbnailData[0]);
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        result.push({
 | 
				
			||||||
 | 
					                            startTime: vttRawData.cues[i].startTime,
 | 
				
			||||||
 | 
					                            endTime: vttRawData.cues[i].endTime,
 | 
				
			||||||
 | 
					                            image: imageUrl,
 | 
				
			||||||
 | 
					                            x: xCoords,
 | 
				
			||||||
 | 
					                            y: yCoords,
 | 
				
			||||||
 | 
					                            w: wCoords,
 | 
				
			||||||
 | 
					                            h: hCoords
 | 
				
			||||||
 | 
					                        });
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    return result;
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const xmlHttpReq = this;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if ((xmlHttpReq.readyState === 4) && (xmlHttpReq.status !== 200)) {
 | 
				
			||||||
 | 
					                    //The response returned an error.
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (!((xmlHttpReq.readyState === 4) && (xmlHttpReq.status === 200))) {
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const textResponse = xmlHttpReq.responseText;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const webVttParser = new window.WebVTTParser();
 | 
				
			||||||
 | 
					                const vttRawData = webVttParser.parse(textResponse);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                playerInstance.timelinePreviewData = convertVttRawData(vttRawData);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.generateTimelinePreviewTags = () => {
 | 
				
			||||||
 | 
					        const progressContainer = playerInstance.domRef.wrapper.querySelector('.fluid_controls_progress_container');
 | 
				
			||||||
 | 
					        const previewContainer = document.createElement('div');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        previewContainer.className = 'fluid_timeline_preview_container';
 | 
				
			||||||
 | 
					        previewContainer.style.display = 'none';
 | 
				
			||||||
 | 
					        previewContainer.style.position = 'absolute';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        progressContainer.appendChild(previewContainer);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        //Shadow is needed to not trigger mouseleave event, that stops showing thumbnails, in case one scrubs a bit too fast and leaves current thumb before new one drawn.
 | 
				
			||||||
 | 
					        const previewContainerShadow = document.createElement('div');
 | 
				
			||||||
 | 
					        previewContainerShadow.className = 'fluid_timeline_preview_container_shadow';
 | 
				
			||||||
 | 
					        previewContainerShadow.style.position = 'absolute';
 | 
				
			||||||
 | 
					        previewContainerShadow.style.display = 'none';
 | 
				
			||||||
 | 
					        previewContainerShadow.style.opacity = 1;
 | 
				
			||||||
 | 
					        progressContainer.appendChild(previewContainerShadow);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.getThumbnailCoordinates = (second) => {
 | 
				
			||||||
 | 
					        if (playerInstance.timelinePreviewData.length) {
 | 
				
			||||||
 | 
					            for (let i = 0; i < playerInstance.timelinePreviewData.length; i++) {
 | 
				
			||||||
 | 
					                if ((second >= playerInstance.timelinePreviewData[i].startTime) && (second <= playerInstance.timelinePreviewData[i].endTime)) {
 | 
				
			||||||
 | 
					                    return playerInstance.timelinePreviewData[i];
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.drawTimelinePreview = (event) => {
 | 
				
			||||||
 | 
					        const timelinePreviewTag = playerInstance.domRef.wrapper.querySelector('.fluid_timeline_preview_container');
 | 
				
			||||||
 | 
					        const timelinePreviewShadow = playerInstance.domRef.wrapper.querySelector('.fluid_timeline_preview_container_shadow');
 | 
				
			||||||
 | 
					        const progressContainer = playerInstance.domRef.wrapper.querySelector('.fluid_controls_progress_container');
 | 
				
			||||||
 | 
					        const totalWidth = progressContainer.clientWidth;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (playerInstance.isCurrentlyPlayingAd) {
 | 
				
			||||||
 | 
					            if (timelinePreviewTag.style.display !== 'none') {
 | 
				
			||||||
 | 
					                timelinePreviewTag.style.display = 'none';
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        //get the hover position
 | 
				
			||||||
 | 
					        const hoverX = playerInstance.getEventOffsetX(event, progressContainer);
 | 
				
			||||||
 | 
					        let hoverSecond = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (totalWidth) {
 | 
				
			||||||
 | 
					            hoverSecond = playerInstance.currentVideoDuration * hoverX / totalWidth;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            //get the corresponding thumbnail coordinates
 | 
				
			||||||
 | 
					            const thumbnailCoordinates = playerInstance.getThumbnailCoordinates(hoverSecond);
 | 
				
			||||||
 | 
					            timelinePreviewShadow.style.width = totalWidth + 'px';
 | 
				
			||||||
 | 
					            timelinePreviewShadow.style.display = 'block';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (thumbnailCoordinates !== false) {
 | 
				
			||||||
 | 
					                timelinePreviewTag.style.width = thumbnailCoordinates.w + 'px';
 | 
				
			||||||
 | 
					                timelinePreviewTag.style.height = thumbnailCoordinates.h + 'px';
 | 
				
			||||||
 | 
					                timelinePreviewShadow.style.height = thumbnailCoordinates.h + 'px';
 | 
				
			||||||
 | 
					                timelinePreviewTag.style.background =
 | 
				
			||||||
 | 
					                    'url(' + thumbnailCoordinates.image + ') no-repeat scroll -' + thumbnailCoordinates.x + 'px -' + thumbnailCoordinates.y + 'px';
 | 
				
			||||||
 | 
					                timelinePreviewTag.style.left = hoverX - (thumbnailCoordinates.w / 2) + 'px';
 | 
				
			||||||
 | 
					                timelinePreviewTag.style.display = 'block';
 | 
				
			||||||
 | 
					                if (!playerInstance.displayOptions.layoutControls.timelinePreview.spriteImage) {
 | 
				
			||||||
 | 
					                    timelinePreviewTag.style.backgroundSize = 'contain';
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                timelinePreviewTag.style.display = 'none';
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.setupThumbnailPreview = () => {
 | 
				
			||||||
 | 
					        let timelinePreview = playerInstance.displayOptions.layoutControls.timelinePreview;
 | 
				
			||||||
 | 
					        if (!timelinePreview || !timelinePreview.type) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let eventOn = 'mousemove';
 | 
				
			||||||
 | 
					        let eventOff = 'mouseleave';
 | 
				
			||||||
 | 
					        if (playerInstance.mobileInfo.userOs) {
 | 
				
			||||||
 | 
					            eventOn = 'touchmove';
 | 
				
			||||||
 | 
					            eventOff = 'touchend';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        playerInstance.domRef.wrapper.querySelector('.fluid_controls_progress_container')
 | 
				
			||||||
 | 
					            .addEventListener(eventOn, playerInstance.drawTimelinePreview.bind(playerInstance), false);
 | 
				
			||||||
 | 
					        playerInstance.domRef.wrapper.querySelector('.fluid_controls_progress_container')
 | 
				
			||||||
 | 
					            .addEventListener(eventOff, function (event) {
 | 
				
			||||||
 | 
					                const progress = playerInstance.domRef.wrapper.querySelector('.fluid_controls_progress_container');
 | 
				
			||||||
 | 
					                if (typeof event.clientX !== 'undefined' && progress.contains(document.elementFromPoint(event.clientX, event.clientY))) {
 | 
				
			||||||
 | 
					                    //False positive (Chrome bug when fast click causes leave event)
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                playerInstance.domRef.wrapper.querySelector('.fluid_timeline_preview_container').style.display = 'none';
 | 
				
			||||||
 | 
					                playerInstance.domRef.wrapper.querySelector('.fluid_timeline_preview_container_shadow').style.display = 'none';
 | 
				
			||||||
 | 
					            }, false);
 | 
				
			||||||
 | 
					        playerInstance.generateTimelinePreviewTags();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if ('VTT' === timelinePreview.type && typeof timelinePreview.file === 'string') {
 | 
				
			||||||
 | 
					            import(/* webpackChunkName: "webvtt" */ '../../vendor/webvtt').then((it) => {
 | 
				
			||||||
 | 
					                window.WebVTTParser = it.default;
 | 
				
			||||||
 | 
					                playerInstance.setupThumbnailPreviewVtt();
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        } else if ('static' === timelinePreview.type && typeof timelinePreview.frames === 'object') {
 | 
				
			||||||
 | 
					            timelinePreview.spriteImage = true;
 | 
				
			||||||
 | 
					            playerInstance.timelinePreviewData = timelinePreview.frames;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            throw 'Invalid thumbnail-preview - type must be VTT or static';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        playerInstance.showTimeOnHover = false;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										326
									
								
								client/fluid-player/src/modules/utils.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,326 @@
 | 
				
			||||||
 | 
					export default function (playerInstance, options) {
 | 
				
			||||||
 | 
					    playerInstance.isTouchDevice = () => {
 | 
				
			||||||
 | 
					        return !!('ontouchstart' in window        // works on most browsers
 | 
				
			||||||
 | 
					            || navigator.maxTouchPoints);       // works on IE10/11 and Surface
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Distinguishes iOS from Android devices and the OS version.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * This should be avoided in favor of capability detection.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @deprecated deprecated as of v3.0
 | 
				
			||||||
 | 
					     * @returns object
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    playerInstance.getMobileOs = () => {
 | 
				
			||||||
 | 
					        const ua = navigator.userAgent || '';
 | 
				
			||||||
 | 
					        const result = {device: false, userOs: false, userOsVer: false, userOsMajor: false};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let versionIndex;
 | 
				
			||||||
 | 
					        // determine OS
 | 
				
			||||||
 | 
					        if (ua.match(/Android/i)) {
 | 
				
			||||||
 | 
					            result.userOs = 'Android';
 | 
				
			||||||
 | 
					            versionIndex = ua.indexOf('Android ');
 | 
				
			||||||
 | 
					        } else if (ua.match(/iPhone/i)) {
 | 
				
			||||||
 | 
					            result.device = 'iPhone';
 | 
				
			||||||
 | 
					            result.userOs = 'iOS';
 | 
				
			||||||
 | 
					            versionIndex = ua.indexOf('OS ');
 | 
				
			||||||
 | 
					        } else if (ua.match(/iPad/i)) {
 | 
				
			||||||
 | 
					            result.device = 'iPad';
 | 
				
			||||||
 | 
					            result.userOs = 'iOS';
 | 
				
			||||||
 | 
					            versionIndex = ua.indexOf('OS ');
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            result.userOs = false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // determine version
 | 
				
			||||||
 | 
					        if ('iOS' === result.userOs && versionIndex > -1) {
 | 
				
			||||||
 | 
					            const userOsTemp = ua.substr(versionIndex + 3);
 | 
				
			||||||
 | 
					            const indexOfEndOfVersion = userOsTemp.indexOf(' ');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (indexOfEndOfVersion !== -1) {
 | 
				
			||||||
 | 
					                result.userOsVer = userOsTemp.substring(0, userOsTemp.indexOf(' ')).replace(/_/g, '.');
 | 
				
			||||||
 | 
					                result.userOsMajor = parseInt(result.userOsVer);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else if ('Android' === result.userOs && versionIndex > -1) {
 | 
				
			||||||
 | 
					            result.userOsVer = ua.substr(versionIndex + 8, 3);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            result.userOsVer = false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return result;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Browser detection.
 | 
				
			||||||
 | 
					     * This should be avoided in favor of capability detection.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @deprecated deprecated as of v3.0
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @returns object
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    playerInstance.getBrowserVersion = () => {
 | 
				
			||||||
 | 
					        const ua = navigator.userAgent || '';
 | 
				
			||||||
 | 
					        const result = {browserName: false, fullVersion: false, majorVersion: false, userOsMajor: false};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let idx, uaindex;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            result.browserName = navigator.appName;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if ((idx = ua.indexOf('OPR/')) !== -1) {
 | 
				
			||||||
 | 
					                result.browserName = 'Opera';
 | 
				
			||||||
 | 
					                result.fullVersion = ua.substring(idx + 4);
 | 
				
			||||||
 | 
					            } else if ((idx = ua.indexOf('Opera')) !== -1) {
 | 
				
			||||||
 | 
					                result.browserName = 'Opera';
 | 
				
			||||||
 | 
					                result.fullVersion = ua.substring(idx + 6);
 | 
				
			||||||
 | 
					                if ((idx = ua.indexOf('Version')) !== -1)
 | 
				
			||||||
 | 
					                    result.fullVersion = ua.substring(idx + 8);
 | 
				
			||||||
 | 
					            } else if ((idx = ua.indexOf('MSIE')) !== -1) {
 | 
				
			||||||
 | 
					                result.browserName = 'Microsoft Internet Explorer';
 | 
				
			||||||
 | 
					                result.fullVersion = ua.substring(idx + 5);
 | 
				
			||||||
 | 
					            } else if ((idx = ua.indexOf('Chrome')) !== -1) {
 | 
				
			||||||
 | 
					                result.browserName = 'Google Chrome';
 | 
				
			||||||
 | 
					                result.fullVersion = ua.substring(idx + 7);
 | 
				
			||||||
 | 
					            } else if ((idx = ua.indexOf('Safari')) !== -1) {
 | 
				
			||||||
 | 
					                result.browserName = 'Safari';
 | 
				
			||||||
 | 
					                result.fullVersion = ua.substring(idx + 7);
 | 
				
			||||||
 | 
					                if ((idx = ua.indexOf('Version')) !== -1)
 | 
				
			||||||
 | 
					                    result.fullVersion = ua.substring(idx + 8);
 | 
				
			||||||
 | 
					            } else if ((idx = ua.indexOf('Firefox')) !== -1) {
 | 
				
			||||||
 | 
					                result.browserName = 'Mozilla Firefox';
 | 
				
			||||||
 | 
					                result.fullVersion = ua.substring(idx + 8);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Others "name/version" is at the end of userAgent
 | 
				
			||||||
 | 
					            else if ((uaindex = ua.lastIndexOf(' ') + 1) < (idx = ua.lastIndexOf('/'))) {
 | 
				
			||||||
 | 
					                result.browserName = ua.substring(uaindex, idx);
 | 
				
			||||||
 | 
					                result.fullVersion = ua.substring(idx + 1);
 | 
				
			||||||
 | 
					                if (result.browserName.toLowerCase() === result.browserName.toUpperCase()) {
 | 
				
			||||||
 | 
					                    result.browserName = navigator.appName;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // trim the fullVersion string at semicolon/space if present
 | 
				
			||||||
 | 
					            if ((uaindex = result.fullVersion.indexOf(';')) !== -1) {
 | 
				
			||||||
 | 
					                result.fullVersion = result.fullVersion.substring(0, uaindex);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if ((uaindex = result.fullVersion.indexOf(' ')) !== -1) {
 | 
				
			||||||
 | 
					                result.fullVersion = result.fullVersion.substring(0, uaindex);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            result.majorVersion = parseInt('' + result.fullVersion, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (isNaN(result.majorVersion)) {
 | 
				
			||||||
 | 
					                result.fullVersion = '' + parseFloat(navigator.appVersion);
 | 
				
			||||||
 | 
					                result.majorVersion = parseInt(navigator.appVersion, 10);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } catch (e) {
 | 
				
			||||||
 | 
					            //Return default obj.
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return result;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.compareVersion = (v1, v2) => {
 | 
				
			||||||
 | 
					        if (typeof v1 !== 'string') return false;
 | 
				
			||||||
 | 
					        if (typeof v2 !== 'string') return false;
 | 
				
			||||||
 | 
					        v1 = v1.split('.');
 | 
				
			||||||
 | 
					        v2 = v2.split('.');
 | 
				
			||||||
 | 
					        const k = Math.min(v1.length, v2.length);
 | 
				
			||||||
 | 
					        for (let i = 0; i < k; ++i) {
 | 
				
			||||||
 | 
					            v1[i] = parseInt(v1[i], 10);
 | 
				
			||||||
 | 
					            v2[i] = parseInt(v2[i], 10);
 | 
				
			||||||
 | 
					            if (v1[i] > v2[i]) return 1;
 | 
				
			||||||
 | 
					            if (v1[i] < v2[i]) return -1;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return v1.length === v2.length ? 0 : (v1.length < v2.length ? -1 : 1);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.convertTimeStringToSeconds = (str) => {
 | 
				
			||||||
 | 
					        if (!(str && str.match(/^(\d){2}(:[0-5][0-9]){2}(.(\d){1,3})?$/))) {
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const timeParts = str.split(':');
 | 
				
			||||||
 | 
					        return ((parseInt(timeParts[0], 10)) * 3600) + ((parseInt(timeParts[1], 10)) * 60) + (parseInt(timeParts[2], 10));
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Format time to hh:mm:ss
 | 
				
			||||||
 | 
					    playerInstance.formatTime = (duration) => {
 | 
				
			||||||
 | 
					        const formatDateObj = new Date(duration * 1000);
 | 
				
			||||||
 | 
					        const formatHours = playerInstance.pad(formatDateObj.getUTCHours());
 | 
				
			||||||
 | 
					        const formatMinutes = playerInstance.pad(formatDateObj.getUTCMinutes());
 | 
				
			||||||
 | 
					        const formatSeconds = playerInstance.pad(formatDateObj.getSeconds());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return formatHours >= 1
 | 
				
			||||||
 | 
					            ? formatHours + ':' + formatMinutes + ':' + formatSeconds
 | 
				
			||||||
 | 
					            : formatMinutes + ':' + formatSeconds;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.pad = (value) => {
 | 
				
			||||||
 | 
					        if (value < 10) {
 | 
				
			||||||
 | 
					            return '0' + value;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return value;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Checks if element is fully visible in the viewport
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param {Element} element
 | 
				
			||||||
 | 
					     * @returns {boolean|null}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    playerInstance.isElementVisible = (element) => {
 | 
				
			||||||
 | 
					        if (!element) { return null; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const rect = element.getBoundingClientRect();
 | 
				
			||||||
 | 
					        return (
 | 
				
			||||||
 | 
					            rect.top >= 0 &&
 | 
				
			||||||
 | 
					            rect.left >= 0 &&
 | 
				
			||||||
 | 
					            rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
 | 
				
			||||||
 | 
					            rect.right <= (window.innerWidth || document.documentElement.clientWidth)
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.observe = () => {
 | 
				
			||||||
 | 
					        var observer = new IntersectionObserver(
 | 
				
			||||||
 | 
					            function (entries) {
 | 
				
			||||||
 | 
					                entries.forEach(function (entry) {
 | 
				
			||||||
 | 
					                    if (entry.intersectionRatio >= 0.5) {
 | 
				
			||||||
 | 
					                        playerInstance.domRef.player.inView = true;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (entry.intersectionRatio == 0 && entry.target.glast) {
 | 
				
			||||||
 | 
					                        playerInstance.domRef.player.inView = false;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                threshold: [0.0, 0.5],
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        observer.observe(playerInstance.domRef.wrapper);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Throttles callback by time
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param callback
 | 
				
			||||||
 | 
					     * @param time
 | 
				
			||||||
 | 
					     * @returns {function(): void}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    playerInstance.throttle = function throttle(callback, time) {
 | 
				
			||||||
 | 
					        let throttleControl = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return function () {
 | 
				
			||||||
 | 
					            if (!throttleControl) {
 | 
				
			||||||
 | 
					                callback.apply(this, arguments);
 | 
				
			||||||
 | 
					                throttleControl = true;
 | 
				
			||||||
 | 
					                setTimeout(function () {
 | 
				
			||||||
 | 
					                    throttleControl = false;
 | 
				
			||||||
 | 
					                }, time);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.getImageTwoMostProminentColours = (imageUrl) => {
 | 
				
			||||||
 | 
					        return new Promise((resolve, reject) => {
 | 
				
			||||||
 | 
					            const img = new Image();
 | 
				
			||||||
 | 
					            img.crossOrigin = 'Anonymous';
 | 
				
			||||||
 | 
					            img.src = imageUrl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            img.onload = () => {
 | 
				
			||||||
 | 
					                const canvas = document.createElement('canvas');
 | 
				
			||||||
 | 
					                const ctx = canvas.getContext('2d');
 | 
				
			||||||
 | 
					                canvas.width = img.width;
 | 
				
			||||||
 | 
					                canvas.height = img.height;
 | 
				
			||||||
 | 
					                ctx.drawImage(img, 0, 0, img.width, img.height);
 | 
				
			||||||
 | 
					                const imageData = ctx.getImageData(0, 0, img.width, img.height).data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const colorCount = {};
 | 
				
			||||||
 | 
					                for (let i = 0; i < imageData.length; i += 4) {
 | 
				
			||||||
 | 
					                    const r = imageData[i];
 | 
				
			||||||
 | 
					                    const g = imageData[i + 1];
 | 
				
			||||||
 | 
					                    const b = imageData[i + 2];
 | 
				
			||||||
 | 
					                    const color = `rgb(${r},${g},${b})`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (colorCount[color]) {
 | 
				
			||||||
 | 
					                        colorCount[color]++;
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        colorCount[color] = 1;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const rgbToHsl = (r, g, b) => {
 | 
				
			||||||
 | 
					                    r /= 255, g /= 255, b /= 255;
 | 
				
			||||||
 | 
					                    const max = Math.max(r, g, b), min = Math.min(r, g, b);
 | 
				
			||||||
 | 
					                    let h, s, l = (max + min) / 2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (max === min) {
 | 
				
			||||||
 | 
					                        h = s = 0;
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        const d = max - min;
 | 
				
			||||||
 | 
					                        s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
 | 
				
			||||||
 | 
					                        switch (max) {
 | 
				
			||||||
 | 
					                            case r: h = (g - b) / d + (g < b ? 6 : 0); break;
 | 
				
			||||||
 | 
					                            case g: h = (b - r) / d + 2; break;
 | 
				
			||||||
 | 
					                            case b: h = (r - g) / d + 4; break;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        h /= 6;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    return [h, s, l];
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const sortedColors = Object.keys(colorCount).map(color => {
 | 
				
			||||||
 | 
					                    const [r, g, b] = color.match(/\d+/g).map(Number);
 | 
				
			||||||
 | 
					                    const [h, s, l] = rgbToHsl(r, g, b);
 | 
				
			||||||
 | 
					                    return { color, h, s, l, score: s + (1 - Math.abs(2 * l - 1)) };
 | 
				
			||||||
 | 
					                }).sort((a, b) => b.score - a.score);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const isCloseToBlack = (color) => {
 | 
				
			||||||
 | 
					                    const rgb = color.match(/\d+/g).map(Number);
 | 
				
			||||||
 | 
					                    const blackThreshold = 40;
 | 
				
			||||||
 | 
					                    return rgb[0] < blackThreshold && rgb[1] < blackThreshold && rgb[2] < blackThreshold;
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const minHueDifference = 0.1;
 | 
				
			||||||
 | 
					                const minSaturationDifference = 0.1;
 | 
				
			||||||
 | 
					                const minLightnessDifference = 0.1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                let mostVibrantColors = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                for (const colorObj of sortedColors) {
 | 
				
			||||||
 | 
					                    if (mostVibrantColors.length === 2) break;
 | 
				
			||||||
 | 
					                    if (!isCloseToBlack(colorObj.color)) {
 | 
				
			||||||
 | 
					                        const enoughDifference = mostVibrantColors.every(existingColor => {
 | 
				
			||||||
 | 
					                            const hueDifference = Math.abs(colorObj.h - existingColor.h);
 | 
				
			||||||
 | 
					                            const saturationDifference = Math.abs(colorObj.s - existingColor.s);
 | 
				
			||||||
 | 
					                            const lightnessDifference = Math.abs(colorObj.l - existingColor.l);
 | 
				
			||||||
 | 
					                            return (
 | 
				
			||||||
 | 
					                                hueDifference >= minHueDifference &&
 | 
				
			||||||
 | 
					                                saturationDifference >= minSaturationDifference &&
 | 
				
			||||||
 | 
					                                lightnessDifference >= minLightnessDifference
 | 
				
			||||||
 | 
					                            );
 | 
				
			||||||
 | 
					                        });
 | 
				
			||||||
 | 
					                        if (enoughDifference) {
 | 
				
			||||||
 | 
					                            mostVibrantColors.push(colorObj);
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (mostVibrantColors.length < 2) {
 | 
				
			||||||
 | 
					                    mostVibrantColors = sortedColors.slice(0, 2);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                resolve(mostVibrantColors.map(colorObj => colorObj.color));
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            img.onerror = () => {
 | 
				
			||||||
 | 
					                reject(new Error('Failed to load image'));
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										1122
									
								
								client/fluid-player/src/modules/vast.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										543
									
								
								client/fluid-player/src/modules/vpaid.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,543 @@
 | 
				
			||||||
 | 
					// VPAID support module
 | 
				
			||||||
 | 
					export default function (playerInstance, options) {
 | 
				
			||||||
 | 
					    const callbacks = {
 | 
				
			||||||
 | 
					        AdStarted: () => playerInstance.onStartVpaidAd,
 | 
				
			||||||
 | 
					        AdStopped: () => playerInstance.onStopVpaidAd,
 | 
				
			||||||
 | 
					        AdSkipped: () => playerInstance.onSkipVpaidAd,
 | 
				
			||||||
 | 
					        AdLoaded: () => playerInstance.onVpaidAdLoaded,
 | 
				
			||||||
 | 
					        AdLinearChange: () => playerInstance.onVpaidAdLinearChange,
 | 
				
			||||||
 | 
					        AdSizeChange: () => playerInstance.onVpaidAdSizeChange,
 | 
				
			||||||
 | 
					        AdExpandedChange: () => playerInstance.onVpaidAdExpandedChange,
 | 
				
			||||||
 | 
					        AdSkippableStateChange: () => playerInstance.onVpaidAdSkippableStateChange,
 | 
				
			||||||
 | 
					        AdDurationChange: () => playerInstance.onVpaidAdDurationChange,
 | 
				
			||||||
 | 
					        AdRemainingTimeChange: () => playerInstance.onVpaidAdRemainingTimeChange,
 | 
				
			||||||
 | 
					        AdVolumeChange: () => playerInstance.onVpaidAdVolumeChange,
 | 
				
			||||||
 | 
					        AdImpression: () => playerInstance.onVpaidAdImpression,
 | 
				
			||||||
 | 
					        AdClickThru: () => playerInstance.onVpaidAdClickThru,
 | 
				
			||||||
 | 
					        AdInteraction: () => playerInstance.onVpaidAdInteraction,
 | 
				
			||||||
 | 
					        AdVideoStart: () => playerInstance.onVpaidAdVideoStart,
 | 
				
			||||||
 | 
					        AdVideoFirstQuartile: () => playerInstance.onVpaidAdVideoFirstQuartile,
 | 
				
			||||||
 | 
					        AdVideoMidpoint: () => playerInstance.onVpaidAdVideoMidpoint,
 | 
				
			||||||
 | 
					        AdVideoThirdQuartile: () => playerInstance.onVpaidAdVideoThirdQuartile,
 | 
				
			||||||
 | 
					        AdVideoComplete: () => playerInstance.onVpaidAdVideoComplete,
 | 
				
			||||||
 | 
					        AdUserAcceptInvitation: () => playerInstance.onVpaidAdUserAcceptInvitation,
 | 
				
			||||||
 | 
					        AdUserMinimize: () => playerInstance.onVpaidAdUserMinimize,
 | 
				
			||||||
 | 
					        AdUserClose: () => playerInstance.onVpaidAdUserClose,
 | 
				
			||||||
 | 
					        AdPaused: () => playerInstance.onVpaidAdPaused,
 | 
				
			||||||
 | 
					        AdPlaying: () => playerInstance.onVpaidAdPlaying,
 | 
				
			||||||
 | 
					        AdError: () => playerInstance.onVpaidAdError,
 | 
				
			||||||
 | 
					        AdLog: () => playerInstance.onVpaidAdLog
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.checkVPAIDInterface = (vpaidAdUnit) => {
 | 
				
			||||||
 | 
					        const VPAIDCreative = vpaidAdUnit;
 | 
				
			||||||
 | 
					        // checks if all the mandatory params present
 | 
				
			||||||
 | 
					        return !!(VPAIDCreative.handshakeVersion && typeof VPAIDCreative.handshakeVersion == "function"
 | 
				
			||||||
 | 
					            && VPAIDCreative.initAd && typeof VPAIDCreative.initAd == "function"
 | 
				
			||||||
 | 
					            && VPAIDCreative.startAd && typeof VPAIDCreative.startAd == "function"
 | 
				
			||||||
 | 
					            && VPAIDCreative.stopAd && typeof VPAIDCreative.stopAd == "function"
 | 
				
			||||||
 | 
					            && VPAIDCreative.skipAd && typeof VPAIDCreative.skipAd == "function"
 | 
				
			||||||
 | 
					            && VPAIDCreative.resizeAd && typeof VPAIDCreative.resizeAd == "function"
 | 
				
			||||||
 | 
					            && VPAIDCreative.pauseAd && typeof VPAIDCreative.pauseAd == "function"
 | 
				
			||||||
 | 
					            && VPAIDCreative.resumeAd && typeof VPAIDCreative.resumeAd == "function"
 | 
				
			||||||
 | 
					            && VPAIDCreative.expandAd && typeof VPAIDCreative.expandAd == "function"
 | 
				
			||||||
 | 
					            && VPAIDCreative.collapseAd && typeof VPAIDCreative.collapseAd == "function"
 | 
				
			||||||
 | 
					            && VPAIDCreative.subscribe && typeof VPAIDCreative.subscribe == "function"
 | 
				
			||||||
 | 
					            && VPAIDCreative.unsubscribe && typeof VPAIDCreative.unsubscribe == "function");
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Callback for AdPaused
 | 
				
			||||||
 | 
					    playerInstance.onVpaidAdPaused = () => {
 | 
				
			||||||
 | 
					        playerInstance.vpaidTimeoutTimerClear();
 | 
				
			||||||
 | 
					        playerInstance.debugMessage("onAdPaused");
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Callback for AdPlaying
 | 
				
			||||||
 | 
					    playerInstance.onVpaidAdPlaying = () => {
 | 
				
			||||||
 | 
					        playerInstance.vpaidTimeoutTimerClear();
 | 
				
			||||||
 | 
					        playerInstance.debugMessage("onAdPlaying");
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Callback for AdError
 | 
				
			||||||
 | 
					    playerInstance.onVpaidAdError = (message) => {
 | 
				
			||||||
 | 
					        playerInstance.debugMessage("onAdError: " + message);
 | 
				
			||||||
 | 
					        playerInstance.vpaidTimeoutTimerClear();
 | 
				
			||||||
 | 
					        playerInstance.onVpaidEnded();
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Callback for AdLog
 | 
				
			||||||
 | 
					    playerInstance.onVpaidAdLog = (message) => {
 | 
				
			||||||
 | 
					        playerInstance.debugMessage("onAdLog: " + message);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Callback for AdUserAcceptInvitation
 | 
				
			||||||
 | 
					    playerInstance.onVpaidAdUserAcceptInvitation = () => {
 | 
				
			||||||
 | 
					        playerInstance.debugMessage("onAdUserAcceptInvitation");
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Callback for AdUserMinimize
 | 
				
			||||||
 | 
					    playerInstance.onVpaidAdUserMinimize = () => {
 | 
				
			||||||
 | 
					        playerInstance.debugMessage("onAdUserMinimize");
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Callback for AdUserClose
 | 
				
			||||||
 | 
					    playerInstance.onVpaidAdUserClose = () => {
 | 
				
			||||||
 | 
					        playerInstance.debugMessage("onAdUserClose");
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Callback for AdUserClose
 | 
				
			||||||
 | 
					    playerInstance.onVpaidAdSkippableStateChange = () => {
 | 
				
			||||||
 | 
					        if (!playerInstance.vpaidAdUnit) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        playerInstance.debugMessage("Ad Skippable State Changed to: " + playerInstance.vpaidAdUnit.getAdSkippableState());
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Callback for AdUserClose
 | 
				
			||||||
 | 
					    playerInstance.onVpaidAdExpandedChange = () => {
 | 
				
			||||||
 | 
					        if (!playerInstance.vpaidAdUnit) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        playerInstance.debugMessage("Ad Expanded Changed to: " + playerInstance.vpaidAdUnit.getAdExpanded());
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Pass through for getAdExpanded
 | 
				
			||||||
 | 
					    playerInstance.getVpaidAdExpanded = () => {
 | 
				
			||||||
 | 
					        playerInstance.debugMessage("getAdExpanded");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!playerInstance.vpaidAdUnit) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return playerInstance.vpaidAdUnit.getAdExpanded();
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Pass through for getAdSkippableState
 | 
				
			||||||
 | 
					    playerInstance.getVpaidAdSkippableState = () => {
 | 
				
			||||||
 | 
					        playerInstance.debugMessage("getAdSkippableState");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!playerInstance.vpaidAdUnit) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return playerInstance.vpaidAdUnit.getAdSkippableState();
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Callback for AdSizeChange
 | 
				
			||||||
 | 
					    playerInstance.onVpaidAdSizeChange = () => {
 | 
				
			||||||
 | 
					        if (!playerInstance.vpaidAdUnit) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        playerInstance.debugMessage("Ad size changed to: w=" + playerInstance.vpaidAdUnit.getAdWidth() + " h=" + playerInstance.vpaidAdUnit.getAdHeight());
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Callback for AdDurationChange
 | 
				
			||||||
 | 
					    playerInstance.onVpaidAdDurationChange = () => {
 | 
				
			||||||
 | 
					        if (!playerInstance.vpaidAdUnit) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        playerInstance.debugMessage("Ad Duration Changed to: " + playerInstance.vpaidAdUnit.getAdDuration());
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Callback for AdRemainingTimeChange
 | 
				
			||||||
 | 
					    playerInstance.onVpaidAdRemainingTimeChange = () => {
 | 
				
			||||||
 | 
					        if (!playerInstance.vpaidAdUnit) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        playerInstance.debugMessage("Ad Remaining Time Changed to: " + playerInstance.vpaidAdUnit.getAdRemainingTime());
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Pass through for getAdRemainingTime
 | 
				
			||||||
 | 
					    playerInstance.getVpaidAdRemainingTime = () => {
 | 
				
			||||||
 | 
					        playerInstance.debugMessage("getAdRemainingTime");
 | 
				
			||||||
 | 
					        if (!playerInstance.vpaidAdUnit) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return playerInstance.vpaidAdUnit.getAdRemainingTime();
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Callback for AdImpression
 | 
				
			||||||
 | 
					    playerInstance.onVpaidAdImpression = () => {
 | 
				
			||||||
 | 
					        playerInstance.debugMessage("Ad Impression");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        //Announce the impressions
 | 
				
			||||||
 | 
					        playerInstance.trackSingleEvent('impression');
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Callback for AdClickThru
 | 
				
			||||||
 | 
					    playerInstance.onVpaidAdClickThru = (url, id, playerHandles) => {
 | 
				
			||||||
 | 
					        playerInstance.debugMessage("Clickthrough portion of the ad was clicked");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // if playerHandles flag is set to true
 | 
				
			||||||
 | 
					        // then player need to open click thorough url in new window
 | 
				
			||||||
 | 
					        if (playerHandles) {
 | 
				
			||||||
 | 
					            window.open(playerInstance.vastOptions.clickthroughUrl);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        playerInstance.pauseVpaidAd();
 | 
				
			||||||
 | 
					        // fire click tracking
 | 
				
			||||||
 | 
					        playerInstance.callUris(playerInstance.vastOptions.clicktracking);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Callback for AdInteraction
 | 
				
			||||||
 | 
					    playerInstance.onVpaidAdInteraction = (id) => {
 | 
				
			||||||
 | 
					        playerInstance.debugMessage("A non-clickthrough event has occured");
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Callback for AdVideoStart
 | 
				
			||||||
 | 
					    playerInstance.onVpaidAdVideoStart = () => {
 | 
				
			||||||
 | 
					        playerInstance.debugMessage("Video 0% completed");
 | 
				
			||||||
 | 
					        playerInstance.trackSingleEvent('start');
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Callback for AdUserClose
 | 
				
			||||||
 | 
					    playerInstance.onVpaidAdVideoFirstQuartile = () => {
 | 
				
			||||||
 | 
					        playerInstance.debugMessage("Video 25% completed");
 | 
				
			||||||
 | 
					        playerInstance.trackSingleEvent('firstQuartile');
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Callback for AdUserClose
 | 
				
			||||||
 | 
					    playerInstance.onVpaidAdVideoMidpoint = () => {
 | 
				
			||||||
 | 
					        playerInstance.debugMessage("Video 50% completed");
 | 
				
			||||||
 | 
					        playerInstance.trackSingleEvent('midpoint');
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Callback for AdUserClose
 | 
				
			||||||
 | 
					    playerInstance.onVpaidAdVideoThirdQuartile = () => {
 | 
				
			||||||
 | 
					        playerInstance.debugMessage("Video 75% completed");
 | 
				
			||||||
 | 
					        playerInstance.trackSingleEvent('thirdQuartile');
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Callback for AdVideoComplete
 | 
				
			||||||
 | 
					    playerInstance.onVpaidAdVideoComplete = () => {
 | 
				
			||||||
 | 
					        playerInstance.debugMessage("Video 100% completed");
 | 
				
			||||||
 | 
					        playerInstance.trackSingleEvent('complete');
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Callback for AdLinearChange
 | 
				
			||||||
 | 
					    playerInstance.onVpaidAdLinearChange = () => {
 | 
				
			||||||
 | 
					        const vpaidNonLinearSlot = playerInstance.domRef.wrapper.getElementsByClassName("fluid_vpaidNonLinear_ad")[0];
 | 
				
			||||||
 | 
					        const closeBtn = playerInstance.domRef.wrapper.querySelector('.close_button');
 | 
				
			||||||
 | 
					        const adListId = vpaidNonLinearSlot.getAttribute('adlistid');
 | 
				
			||||||
 | 
					        playerInstance.debugMessage("Ad linear has changed: " + playerInstance.vpaidAdUnit.getAdLinear());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!playerInstance.vpaidAdUnit.getAdLinear()) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        playerInstance.backupMainVideoContentTime(adListId.split('_')[0]);
 | 
				
			||||||
 | 
					        playerInstance.isCurrentlyPlayingAd = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (closeBtn) {
 | 
				
			||||||
 | 
					            closeBtn.remove();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        vpaidNonLinearSlot.className = 'fluid_vpaid_slot';
 | 
				
			||||||
 | 
					        playerInstance.domRef.player.loop = false;
 | 
				
			||||||
 | 
					        playerInstance.domRef.player.removeAttribute('controls');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const progressbarContainer = playerInstance.domRef.player.parentNode.getElementsByClassName('fluid_controls_currentprogress');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (let i = 0; i < progressbarContainer.length; i++) {
 | 
				
			||||||
 | 
					            progressbarContainer[i].style.backgroundColor = playerInstance.displayOptions.layoutControls.adProgressColor;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        playerInstance.toggleLoader(false);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Pass through for getAdLinear
 | 
				
			||||||
 | 
					    playerInstance.getVpaidAdLinear = () => {
 | 
				
			||||||
 | 
					        playerInstance.debugMessage("getAdLinear");
 | 
				
			||||||
 | 
					        return playerInstance.vpaidAdUnit.getAdLinear();
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Pass through for startAd()
 | 
				
			||||||
 | 
					    playerInstance.startVpaidAd = () => {
 | 
				
			||||||
 | 
					        playerInstance.debugMessage("startAd");
 | 
				
			||||||
 | 
					        playerInstance.vpaidTimeoutTimerStart();
 | 
				
			||||||
 | 
					        playerInstance.vpaidAdUnit.startAd();
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Callback for AdLoaded
 | 
				
			||||||
 | 
					    playerInstance.onVpaidAdLoaded = () => {
 | 
				
			||||||
 | 
					        playerInstance.debugMessage("ad has been loaded");
 | 
				
			||||||
 | 
					        // start the video play as vpaid is loaded successfully
 | 
				
			||||||
 | 
					        playerInstance.vpaidTimeoutTimerClear();
 | 
				
			||||||
 | 
					        playerInstance.startVpaidAd();
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Callback for StartAd()
 | 
				
			||||||
 | 
					    playerInstance.onStartVpaidAd = () => {
 | 
				
			||||||
 | 
					        playerInstance.debugMessage("Ad has started");
 | 
				
			||||||
 | 
					        playerInstance.vpaidTimeoutTimerClear();
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Pass through for stopAd()
 | 
				
			||||||
 | 
					    playerInstance.stopVpaidAd = () => {
 | 
				
			||||||
 | 
					        playerInstance.vpaidTimeoutTimerStart();
 | 
				
			||||||
 | 
					        playerInstance.vpaidAdUnit.stopAd();
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Hard Pass through for stopAd() excluding deleteOtherVpaidAdsApart
 | 
				
			||||||
 | 
					    playerInstance.hardStopVpaidAd = (ad) => {
 | 
				
			||||||
 | 
					        // this is hard stop of vpaid ads
 | 
				
			||||||
 | 
					        // we delete all the vpaid assets so the new one can be loaded
 | 
				
			||||||
 | 
					        // delete all assets apart from the ad from deleteOtherVpaidAdsApart
 | 
				
			||||||
 | 
					        if (playerInstance.vpaidAdUnit) {
 | 
				
			||||||
 | 
					            playerInstance.vpaidAdUnit.stopAd();
 | 
				
			||||||
 | 
					            playerInstance.vpaidAdUnit = null;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const vpaidIframes = playerInstance.domRef.wrapper.getElementsByClassName("fluid_vpaid_iframe");
 | 
				
			||||||
 | 
					        const vpaidSlots = playerInstance.domRef.wrapper.getElementsByClassName("fluid_vpaid_slot");
 | 
				
			||||||
 | 
					        const vpaidNonLinearSlots = playerInstance.domRef.wrapper.getElementsByClassName("fluid_vpaidNonLinear_ad");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (let i = 0; i < vpaidIframes.length; i++) {
 | 
				
			||||||
 | 
					            if (vpaidIframes[i].getAttribute('adListId') !== ad.id) {
 | 
				
			||||||
 | 
					                vpaidIframes[i].remove();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (let j = 0; j < vpaidSlots.length; j++) {
 | 
				
			||||||
 | 
					            if (vpaidSlots[j].getAttribute('adListId') !== ad.id) {
 | 
				
			||||||
 | 
					                vpaidSlots[j].remove();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (let k = 0; k < vpaidNonLinearSlots.length; k++) {
 | 
				
			||||||
 | 
					            if (vpaidNonLinearSlots[k].getAttribute('adListId') !== ad.id) {
 | 
				
			||||||
 | 
					                vpaidNonLinearSlots[k].remove();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Callback for AdUserClose
 | 
				
			||||||
 | 
					    playerInstance.onStopVpaidAd = () => {
 | 
				
			||||||
 | 
					        playerInstance.debugMessage("Ad has stopped");
 | 
				
			||||||
 | 
					        playerInstance.vpaidTimeoutTimerClear();
 | 
				
			||||||
 | 
					        playerInstance.onVpaidEnded();
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Callback for AdUserClose
 | 
				
			||||||
 | 
					    playerInstance.onSkipVpaidAd = () => {
 | 
				
			||||||
 | 
					        playerInstance.debugMessage("Ad was skipped");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        playerInstance.vpaidTimeoutTimerClear();
 | 
				
			||||||
 | 
					        playerInstance.onVpaidEnded();
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Passthrough for skipAd
 | 
				
			||||||
 | 
					    playerInstance.skipVpaidAd = () => {
 | 
				
			||||||
 | 
					        playerInstance.vpaidTimeoutTimerStart();
 | 
				
			||||||
 | 
					        if (!playerInstance.vpaidAdUnit) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        playerInstance.vpaidAdUnit.skipAd()
 | 
				
			||||||
 | 
					        playerInstance.vpaidTimeoutTimerClear();
 | 
				
			||||||
 | 
					        playerInstance.onVpaidEnded();
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Passthrough for setAdVolume
 | 
				
			||||||
 | 
					    playerInstance.setVpaidAdVolume = (val) => {
 | 
				
			||||||
 | 
					        if (!playerInstance.vpaidAdUnit) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        playerInstance.vpaidAdUnit.setAdVolume(val);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Passthrough for getAdVolume
 | 
				
			||||||
 | 
					    playerInstance.getVpaidAdVolume = () => {
 | 
				
			||||||
 | 
					        if (!playerInstance.vpaidAdUnit) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return playerInstance.vpaidAdUnit.getAdVolume();
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Callback for AdVolumeChange
 | 
				
			||||||
 | 
					    playerInstance.onVpaidAdVolumeChange = () => {
 | 
				
			||||||
 | 
					        if (!playerInstance.vpaidAdUnit) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        playerInstance.debugMessage("Ad Volume has changed to - " + playerInstance.vpaidAdUnit.getAdVolume());
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.resizeVpaidAuto = () => {
 | 
				
			||||||
 | 
					        if (playerInstance.vastOptions !== null && playerInstance.vastOptions.vpaid && playerInstance.vastOptions.linear) {
 | 
				
			||||||
 | 
					            const adWidth = playerInstance.domRef.player.offsetWidth;
 | 
				
			||||||
 | 
					            const adHeight = playerInstance.domRef.player.offsetHeight;
 | 
				
			||||||
 | 
					            const mode = (playerInstance.fullscreenMode ? 'fullscreen' : 'normal');
 | 
				
			||||||
 | 
					            playerInstance.resizeVpaidAd(adWidth, adHeight, mode);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Passthrough for resizeAd
 | 
				
			||||||
 | 
					    playerInstance.resizeVpaidAd = (width, height, viewMode) => {
 | 
				
			||||||
 | 
					        if (!playerInstance.vpaidAdUnit) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        playerInstance.vpaidAdUnit.resizeAd(width, height, viewMode);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Passthrough for pauseAd()
 | 
				
			||||||
 | 
					    playerInstance.pauseVpaidAd = () => {
 | 
				
			||||||
 | 
					        playerInstance.vpaidTimeoutTimerStart();
 | 
				
			||||||
 | 
					        if (!playerInstance.vpaidAdUnit) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        playerInstance.vpaidAdUnit.pauseAd();
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Passthrough for resumeAd()
 | 
				
			||||||
 | 
					    playerInstance.resumeVpaidAd = () => {
 | 
				
			||||||
 | 
					        playerInstance.vpaidTimeoutTimerStart();
 | 
				
			||||||
 | 
					        if (!playerInstance.vpaidAdUnit) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        playerInstance.vpaidAdUnit.resumeAd();
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Passthrough for expandAd()
 | 
				
			||||||
 | 
					    playerInstance.expandVpaidAd = () => {
 | 
				
			||||||
 | 
					        if (!playerInstance.vpaidAdUnit) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        playerInstance.vpaidAdUnit.expandAd();
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Passthrough for collapseAd()
 | 
				
			||||||
 | 
					    playerInstance.collapseVpaidAd = () => {
 | 
				
			||||||
 | 
					        if (!playerInstance.vpaidAdUnit) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        playerInstance.vpaidAdUnit.collapseAd();
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.vpaidTimeoutTimerClear = () => {
 | 
				
			||||||
 | 
					        if (playerInstance.vpaidTimer) {
 | 
				
			||||||
 | 
					            clearTimeout(playerInstance.vpaidTimer);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // placeholder for timer function
 | 
				
			||||||
 | 
					    playerInstance.vpaidTimeoutTimerStart = () => {
 | 
				
			||||||
 | 
					        // clear previous timer if any
 | 
				
			||||||
 | 
					        playerInstance.vpaidTimeoutTimerClear();
 | 
				
			||||||
 | 
					        playerInstance.vpaidTimer = setTimeout(function () {
 | 
				
			||||||
 | 
					            playerInstance.announceLocalError('901');
 | 
				
			||||||
 | 
					            playerInstance.onVpaidEnded();
 | 
				
			||||||
 | 
					        }, playerInstance.displayOptions.vastOptions.vpaidTimeout);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.vpaidCallbackListenersAttach = () => {
 | 
				
			||||||
 | 
					        // The key of the object is the event name and the value is a reference to the callback function that is registered with the creative
 | 
				
			||||||
 | 
					        // Looping through the object and registering each of the callbacks with the creative
 | 
				
			||||||
 | 
					        for (let eventName in callbacks) {
 | 
				
			||||||
 | 
					            playerInstance.vpaidAdUnit.subscribe(callbacks[eventName](), eventName, playerInstance);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.vpaidCallbackListenersDetach = () => {
 | 
				
			||||||
 | 
					        if (!playerInstance.vpaidAdUnit) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        for (let eventName in callbacks) {
 | 
				
			||||||
 | 
					            playerInstance.vpaidAdUnit.unsubscribe(callbacks[eventName](), eventName, playerInstance);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.loadVpaid = (ad, vpaidJsUrl) => {
 | 
				
			||||||
 | 
					        const vpaidIframe = document.createElement('iframe');
 | 
				
			||||||
 | 
					        vpaidIframe.id = "fp_" + ad.id + "_fluid_vpaid_iframe";
 | 
				
			||||||
 | 
					        vpaidIframe.className = 'fluid_vpaid_iframe';
 | 
				
			||||||
 | 
					        vpaidIframe.setAttribute('adListId', ad.id);
 | 
				
			||||||
 | 
					        vpaidIframe.setAttribute('frameborder', '0');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        playerInstance.domRef.player.parentNode.insertBefore(vpaidIframe, playerInstance.domRef.player.nextSibling);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const vpaidJsScriptElement = document.createElement('script');
 | 
				
			||||||
 | 
					        vpaidJsScriptElement.src = vpaidJsUrl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        vpaidIframe.contentWindow.document.head.append(vpaidJsScriptElement);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // set interval with timeout
 | 
				
			||||||
 | 
					        playerInstance.tempVpaidCounter = 0;
 | 
				
			||||||
 | 
					        playerInstance.getVPAIDAdInterval = setInterval(function () {
 | 
				
			||||||
 | 
					            if (vpaidIframe && vpaidIframe.contentWindow) {
 | 
				
			||||||
 | 
					                const fn = vpaidIframe.contentWindow['getVPAIDAd'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // check if JS is loaded fully in iframe
 | 
				
			||||||
 | 
					                if (fn && typeof fn == 'function') {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (playerInstance.vpaidAdUnit) {
 | 
				
			||||||
 | 
					                        playerInstance.hardStopVpaidAd(ad);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    playerInstance.vpaidAdUnit = fn();
 | 
				
			||||||
 | 
					                    clearInterval(playerInstance.getVPAIDAdInterval);
 | 
				
			||||||
 | 
					                    if (playerInstance.checkVPAIDInterface(playerInstance.vpaidAdUnit)) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        if (playerInstance.getVpaidAdLinear()) {
 | 
				
			||||||
 | 
					                            playerInstance.isCurrentlyPlayingAd = true;
 | 
				
			||||||
 | 
					                            playerInstance.switchPlayerToVpaidMode(ad);
 | 
				
			||||||
 | 
					                        } else {
 | 
				
			||||||
 | 
					                            playerInstance.debugMessage('non linear vpaid ad is loaded');
 | 
				
			||||||
 | 
					                            playerInstance.loadVpaidNonlinearAssets(ad);
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    // video player will wait for 2seconds if vpaid is not loaded, then it will declare vast error and move ahead
 | 
				
			||||||
 | 
					                    playerInstance.tempVpaidCounter++;
 | 
				
			||||||
 | 
					                    if (playerInstance.tempVpaidCounter >= 20) {
 | 
				
			||||||
 | 
					                        clearInterval(playerInstance.getVPAIDAdInterval);
 | 
				
			||||||
 | 
					                        playerInstance.rollsById[ad.rollListId].error = true;
 | 
				
			||||||
 | 
					                        playerInstance.playMainVideoWhenVpaidFails(403);
 | 
				
			||||||
 | 
					                        return false;
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        playerInstance.debugMessage(playerInstance.tempVpaidCounter);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        }, 100);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        playerInstance.destructors.push(() => clearInterval(playerInstance.getVPAIDAdInterval));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.onVpaidEnded = (event) => {
 | 
				
			||||||
 | 
					        if (event) {
 | 
				
			||||||
 | 
					            event.stopImmediatePropagation();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!playerInstance.vpaidAdUnit) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const vpaidSlot = playerInstance.domRef.wrapper.querySelector('.fluid_vpaid_slot');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        playerInstance.vpaidCallbackListenersDetach();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        playerInstance.vpaidAdUnit = null;
 | 
				
			||||||
 | 
					        clearInterval(playerInstance.getVPAIDAdInterval);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!!vpaidSlot) {
 | 
				
			||||||
 | 
					            vpaidSlot.remove();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        playerInstance.checkForNextAd();
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playerInstance.playMainVideoWhenVpaidFails = (errorCode) => {
 | 
				
			||||||
 | 
					        const vpaidSlot = playerInstance.domRef.wrapper.querySelector('.fluid_vpaid_slot');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (vpaidSlot) {
 | 
				
			||||||
 | 
					            vpaidSlot.remove();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        clearInterval(playerInstance.getVPAIDAdInterval);
 | 
				
			||||||
 | 
					        playerInstance.playMainVideoWhenVastFails(errorCode);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										77
									
								
								client/fluid-player/src/polyfills.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,77 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import promisePolyfill from 'es6-promise';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Object.assign polyfill
 | 
				
			||||||
 | 
					if (typeof Object.assign != 'function') {
 | 
				
			||||||
 | 
					    // Must be writable: true, enumerable: false, configurable: true
 | 
				
			||||||
 | 
					    Object.defineProperty(Object, 'assign', {
 | 
				
			||||||
 | 
					        value: function assign(target, varArgs) { // .length of function is 2
 | 
				
			||||||
 | 
					            'use strict';
 | 
				
			||||||
 | 
					            if (target == null) { // TypeError if undefined or null
 | 
				
			||||||
 | 
					                throw new TypeError('Cannot convert undefined or null to object');
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const to = Object(target);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            for (let index = 1; index < arguments.length; index++) {
 | 
				
			||||||
 | 
					                const nextSource = arguments[index];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (nextSource != null) { // Skip over if undefined or null
 | 
				
			||||||
 | 
					                    for (let nextKey in nextSource) {
 | 
				
			||||||
 | 
					                        // Avoid bugs when hasOwnProperty is shadowed
 | 
				
			||||||
 | 
					                        if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
 | 
				
			||||||
 | 
					                            to[nextKey] = nextSource[nextKey];
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return to;
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        writable: true,
 | 
				
			||||||
 | 
					        configurable: true
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CustomEvent polyfill
 | 
				
			||||||
 | 
					(function () {
 | 
				
			||||||
 | 
					    if (typeof globalThis.CustomEvent === 'function') return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function CustomEvent(event, params) {
 | 
				
			||||||
 | 
					        params = params || {bubbles: false, cancelable: false, detail: undefined};
 | 
				
			||||||
 | 
					        const evt = document.createEvent('CustomEvent');
 | 
				
			||||||
 | 
					        evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
 | 
				
			||||||
 | 
					        return evt;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    CustomEvent.prototype = globalThis.Event.prototype;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    globalThis.CustomEvent = CustomEvent;
 | 
				
			||||||
 | 
					})();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// .remove() polyfill
 | 
				
			||||||
 | 
					if (
 | 
				
			||||||
 | 
					    typeof globalThis.Element !== 'undefined' &&
 | 
				
			||||||
 | 
					    typeof globalThis.CharacterData !== 'undefined' &&
 | 
				
			||||||
 | 
					    typeof globalThis.DocumentType !== 'undefined'
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    (function (arr) {
 | 
				
			||||||
 | 
					        arr.forEach(function (item) {
 | 
				
			||||||
 | 
					            if (item.hasOwnProperty('remove')) {
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            Object.defineProperty(item, 'remove', {
 | 
				
			||||||
 | 
					                configurable: true,
 | 
				
			||||||
 | 
					                enumerable: true,
 | 
				
			||||||
 | 
					                writable: true,
 | 
				
			||||||
 | 
					                value: function remove() {
 | 
				
			||||||
 | 
					                    if (this.parentNode === null) {
 | 
				
			||||||
 | 
					                        return;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    this.parentNode.removeChild(this);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    })([Element.prototype, CharacterData.prototype, DocumentType.prototype]);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					promisePolyfill.polyfill();
 | 
				
			||||||
							
								
								
									
										4
									
								
								client/fluid-player/src/static/close-icon.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,4 @@
 | 
				
			||||||
 | 
					<svg fill="#FFF" height="24" width="24" xmlns="http://www.w3.org/2000/svg">
 | 
				
			||||||
 | 
					    <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
 | 
				
			||||||
 | 
					    <path d="M0 0h24v24H0z" fill="none"/>
 | 
				
			||||||
 | 
					</svg>
 | 
				
			||||||
| 
		 After Width: | Height: | Size: 243 B  | 
							
								
								
									
										353
									
								
								client/fluid-player/src/static/fluid-icons.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,353 @@
 | 
				
			||||||
 | 
					<svg
 | 
				
			||||||
 | 
					    xmlns:xlink="http://www.w3.org/1999/xlink"
 | 
				
			||||||
 | 
					    xmlns="http://www.w3.org/2000/svg"
 | 
				
			||||||
 | 
					    width="388.75"
 | 
				
			||||||
 | 
					    height="96"
 | 
				
			||||||
 | 
					    version="1.1"
 | 
				
			||||||
 | 
					    viewBox="0 0 388.75 96"
 | 
				
			||||||
 | 
					    id="svg63">
 | 
				
			||||||
 | 
					    <path
 | 
				
			||||||
 | 
					        id="path4087"
 | 
				
			||||||
 | 
					        d="m 347.64062,35.959 a 6.9826789,6.9826789 0 0 0 6.98438,-6.984375 6.9826789,6.9826789 0 0 0 -6.98438,-6.982422 6.9826789,6.9826789 0 0 0 -6.98242,6.982422 6.9826789,6.9826789 0 0 0 6.98242,6.984375 z m 0,-2.476562 a 4.5078053,4.5078053 0 0 1 -4.50781,-4.507813 4.5078053,4.5078053 0 0 1 4.50781,-4.507812 4.5078053,4.5078053 0 0 1 4.50781,4.507812 4.5078053,4.5078053 0 0 1 -4.50781,4.507813 z"
 | 
				
			||||||
 | 
					        style="fill:#ffffff;fill-opacity:1;stroke-width:1.70873225"
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					    <path
 | 
				
			||||||
 | 
					        style="fill:#ffffff;fill-opacity:1;stroke-width:1.00886583"
 | 
				
			||||||
 | 
					        d="M 273.55469,22 C 272.69375,22 272,22.693749 272,23.554688 V 34.445312 C 272,35.306251 272.69375,36 273.55469,36 h 10.89062 C 285.30625,36 286,35.306251 286,34.445312 V 23.554688 C 286,22.693749 285.30625,22 284.44531,22 Z m 3.61133,4.541016 c 0.22916,0 0.45442,0.02083 0.67578,0.0625 0.22396,0.03906 0.44661,0.09896 0.66797,0.179687 v 1.140625 c -0.19011,-0.130208 -0.38151,-0.226562 -0.57422,-0.289062 -0.19011,-0.0625 -0.38802,-0.09375 -0.59375,-0.09375 -0.39063,0 -0.69532,0.114583 -0.91407,0.34375 -0.21614,0.226562 -0.32421,0.54427 -0.32421,0.953125 0,0.408854 0.10807,0.727864 0.32421,0.957031 0.21875,0.226562 0.52344,0.339844 0.91407,0.339844 0.21875,0 0.42578,-0.03255 0.62109,-0.09766 0.19792,-0.0651 0.38021,-0.161458 0.54688,-0.289062 v 1.144531 c -0.21875,0.08073 -0.44141,0.140625 -0.66797,0.179688 -0.22396,0.04167 -0.44922,0.0625 -0.67578,0.0625 -0.78907,0 -1.40625,-0.201823 -1.85157,-0.605469 -0.44531,-0.40625 -0.66797,-0.970052 -0.66797,-1.691406 0,-0.721355 0.22266,-1.283855 0.66797,-1.6875 0.44532,-0.40625 1.0625,-0.609375 1.85157,-0.609375 z m 4.75,0 c 0.22916,0 0.45442,0.02083 0.67578,0.0625 0.22396,0.03906 0.44661,0.09896 0.66797,0.179687 v 1.140625 c -0.19011,-0.130208 -0.38151,-0.226562 -0.57422,-0.289062 -0.19011,-0.0625 -0.38802,-0.09375 -0.59375,-0.09375 -0.39063,0 -0.69532,0.114583 -0.91407,0.34375 -0.21614,0.226562 -0.32421,0.54427 -0.32421,0.953125 0,0.408854 0.10807,0.727864 0.32421,0.957031 0.21875,0.226562 0.52344,0.339844 0.91407,0.339844 0.21875,0 0.42578,-0.03255 0.62109,-0.09766 0.19792,-0.0651 0.38021,-0.161458 0.54688,-0.289062 v 1.144531 c -0.21875,0.08073 -0.44141,0.140625 -0.66797,0.179688 -0.22396,0.04167 -0.44922,0.0625 -0.67578,0.0625 -0.78907,0 -1.40625,-0.201823 -1.85157,-0.605469 -0.44531,-0.40625 -0.66797,-0.970052 -0.66797,-1.691406 0,-0.721355 0.22266,-1.283855 0.66797,-1.6875 0.44532,-0.40625 1.0625,-0.609375 1.85157,-0.609375 z"
 | 
				
			||||||
 | 
					        id="rect7816"
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					    <g
 | 
				
			||||||
 | 
					        transform="translate(-3702,106)"
 | 
				
			||||||
 | 
					        id="g44">
 | 
				
			||||||
 | 
					        <clipPath
 | 
				
			||||||
 | 
					            id="q"
 | 
				
			||||||
 | 
					            style="clip-rule:evenodd">
 | 
				
			||||||
 | 
					            <path
 | 
				
			||||||
 | 
					                d="m 3702,-106 h 272 v 96 h -272 z"
 | 
				
			||||||
 | 
					                id="path6"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                style="fill:#ffffff"/>
 | 
				
			||||||
 | 
					        </clipPath>
 | 
				
			||||||
 | 
					        <g
 | 
				
			||||||
 | 
					            clip-path="url(#q)"
 | 
				
			||||||
 | 
					            id="g42">
 | 
				
			||||||
 | 
					            <use
 | 
				
			||||||
 | 
					                transform="translate(3757,-86)"
 | 
				
			||||||
 | 
					                xlink:href="#o"
 | 
				
			||||||
 | 
					                id="use9"
 | 
				
			||||||
 | 
					                style="fill:#ffffff"
 | 
				
			||||||
 | 
					                x="0"
 | 
				
			||||||
 | 
					                y="0"
 | 
				
			||||||
 | 
					                width="100%"
 | 
				
			||||||
 | 
					                height="100%"/>
 | 
				
			||||||
 | 
					            <use
 | 
				
			||||||
 | 
					                transform="translate(3757,-48)"
 | 
				
			||||||
 | 
					                xlink:href="#i"
 | 
				
			||||||
 | 
					                id="use11"
 | 
				
			||||||
 | 
					                style="fill:#ffffff"
 | 
				
			||||||
 | 
					                x="0"
 | 
				
			||||||
 | 
					                y="0"
 | 
				
			||||||
 | 
					                width="100%"
 | 
				
			||||||
 | 
					                height="100%"/>
 | 
				
			||||||
 | 
					            <use
 | 
				
			||||||
 | 
					                transform="translate(3830,-84)"
 | 
				
			||||||
 | 
					                xlink:href="#h"
 | 
				
			||||||
 | 
					                id="use13"
 | 
				
			||||||
 | 
					                style="fill:#ffffff"
 | 
				
			||||||
 | 
					                x="0"
 | 
				
			||||||
 | 
					                y="0"
 | 
				
			||||||
 | 
					                width="100%"
 | 
				
			||||||
 | 
					                height="100%"/>
 | 
				
			||||||
 | 
					            <use
 | 
				
			||||||
 | 
					                transform="translate(3723,-86)"
 | 
				
			||||||
 | 
					                xlink:href="#g"
 | 
				
			||||||
 | 
					                id="use15"
 | 
				
			||||||
 | 
					                style="fill:#ffffff"
 | 
				
			||||||
 | 
					                x="0"
 | 
				
			||||||
 | 
					                y="0"
 | 
				
			||||||
 | 
					                width="100%"
 | 
				
			||||||
 | 
					                height="100%"/>
 | 
				
			||||||
 | 
					            <use
 | 
				
			||||||
 | 
					                transform="translate(3723,-48)"
 | 
				
			||||||
 | 
					                xlink:href="#f"
 | 
				
			||||||
 | 
					                id="use17"
 | 
				
			||||||
 | 
					                style="fill:#ffffff"
 | 
				
			||||||
 | 
					                x="0"
 | 
				
			||||||
 | 
					                y="0"
 | 
				
			||||||
 | 
					                width="100%"
 | 
				
			||||||
 | 
					                height="100%"/>
 | 
				
			||||||
 | 
					            <use
 | 
				
			||||||
 | 
					                transform="translate(3795,-46)"
 | 
				
			||||||
 | 
					                xlink:href="#e"
 | 
				
			||||||
 | 
					                id="use19"
 | 
				
			||||||
 | 
					                style="fill:#ffffff"
 | 
				
			||||||
 | 
					                x="0"
 | 
				
			||||||
 | 
					                y="0"
 | 
				
			||||||
 | 
					                width="100%"
 | 
				
			||||||
 | 
					                height="100%"/>
 | 
				
			||||||
 | 
					            <use
 | 
				
			||||||
 | 
					                transform="translate(3831,-46)"
 | 
				
			||||||
 | 
					                xlink:href="#d"
 | 
				
			||||||
 | 
					                id="use21"
 | 
				
			||||||
 | 
					                style="fill:#ffffff"
 | 
				
			||||||
 | 
					                x="0"
 | 
				
			||||||
 | 
					                y="0"
 | 
				
			||||||
 | 
					                width="100%"
 | 
				
			||||||
 | 
					                height="100%"/>
 | 
				
			||||||
 | 
					            <use
 | 
				
			||||||
 | 
					                transform="translate(3865,-44)"
 | 
				
			||||||
 | 
					                xlink:href="#c"
 | 
				
			||||||
 | 
					                id="use23"
 | 
				
			||||||
 | 
					                style="fill:#f2c94c"
 | 
				
			||||||
 | 
					                x="0"
 | 
				
			||||||
 | 
					                y="0"
 | 
				
			||||||
 | 
					                width="100%"
 | 
				
			||||||
 | 
					                height="100%"/>
 | 
				
			||||||
 | 
					            <use
 | 
				
			||||||
 | 
					                transform="translate(3795,-84)"
 | 
				
			||||||
 | 
					                xlink:href="#b"
 | 
				
			||||||
 | 
					                id="use25"
 | 
				
			||||||
 | 
					                style="fill:#ffffff"
 | 
				
			||||||
 | 
					                x="0"
 | 
				
			||||||
 | 
					                y="0"
 | 
				
			||||||
 | 
					                width="100%"
 | 
				
			||||||
 | 
					                height="100%"/>
 | 
				
			||||||
 | 
					            <use
 | 
				
			||||||
 | 
					                transform="translate(3866,-83)"
 | 
				
			||||||
 | 
					                xlink:href="#a"
 | 
				
			||||||
 | 
					                id="use27"
 | 
				
			||||||
 | 
					                style="fill:#ffffff"
 | 
				
			||||||
 | 
					                x="0"
 | 
				
			||||||
 | 
					                y="0"
 | 
				
			||||||
 | 
					                width="100%"
 | 
				
			||||||
 | 
					                height="100%"/>
 | 
				
			||||||
 | 
					            <use
 | 
				
			||||||
 | 
					                transform="translate(3939,-47)"
 | 
				
			||||||
 | 
					                xlink:href="#n"
 | 
				
			||||||
 | 
					                id="use29"
 | 
				
			||||||
 | 
					                style="fill:#ffffff"
 | 
				
			||||||
 | 
					                x="0"
 | 
				
			||||||
 | 
					                y="0"
 | 
				
			||||||
 | 
					                width="100%"
 | 
				
			||||||
 | 
					                height="100%"/>
 | 
				
			||||||
 | 
					            <mask
 | 
				
			||||||
 | 
					                id="p">
 | 
				
			||||||
 | 
					                <use
 | 
				
			||||||
 | 
					                    transform="translate(3902,-46)"
 | 
				
			||||||
 | 
					                    xlink:href="#m"
 | 
				
			||||||
 | 
					                    id="use31"
 | 
				
			||||||
 | 
					                    style="fill:#ffffff"
 | 
				
			||||||
 | 
					                    x="0"
 | 
				
			||||||
 | 
					                    y="0"
 | 
				
			||||||
 | 
					                    width="100%"
 | 
				
			||||||
 | 
					                    height="100%"/>
 | 
				
			||||||
 | 
					            </mask>
 | 
				
			||||||
 | 
					            <g
 | 
				
			||||||
 | 
					                mask="url(#p)"
 | 
				
			||||||
 | 
					                id="g36">
 | 
				
			||||||
 | 
					                <use
 | 
				
			||||||
 | 
					                    transform="translate(3902,-46)"
 | 
				
			||||||
 | 
					                    xlink:href="#l"
 | 
				
			||||||
 | 
					                    id="use34"
 | 
				
			||||||
 | 
					                    style="fill:#ffffff"
 | 
				
			||||||
 | 
					                    x="0"
 | 
				
			||||||
 | 
					                    y="0"
 | 
				
			||||||
 | 
					                    width="100%"
 | 
				
			||||||
 | 
					                    height="100%"/>
 | 
				
			||||||
 | 
					            </g>
 | 
				
			||||||
 | 
					            <use
 | 
				
			||||||
 | 
					                transform="translate(3904,-85)"
 | 
				
			||||||
 | 
					                xlink:href="#k"
 | 
				
			||||||
 | 
					                id="use38"
 | 
				
			||||||
 | 
					                style="fill:#ffffff"
 | 
				
			||||||
 | 
					                x="0"
 | 
				
			||||||
 | 
					                y="0"
 | 
				
			||||||
 | 
					                width="100%"
 | 
				
			||||||
 | 
					                height="100%"/>
 | 
				
			||||||
 | 
					            <use
 | 
				
			||||||
 | 
					                transform="translate(3939,-85)"
 | 
				
			||||||
 | 
					                xlink:href="#j"
 | 
				
			||||||
 | 
					                id="use40"
 | 
				
			||||||
 | 
					                style="fill:#ffffff"
 | 
				
			||||||
 | 
					                x="0"
 | 
				
			||||||
 | 
					                y="0"
 | 
				
			||||||
 | 
					                width="100%"
 | 
				
			||||||
 | 
					                height="100%"/>
 | 
				
			||||||
 | 
					        </g>
 | 
				
			||||||
 | 
					    </g>
 | 
				
			||||||
 | 
					    <defs
 | 
				
			||||||
 | 
					        id="defs61">
 | 
				
			||||||
 | 
					        <path
 | 
				
			||||||
 | 
					            id="o"
 | 
				
			||||||
 | 
					            d="m 0,5.5924 v 5.8152 H 3.7778 L 8.5,16.2537 V 0.7467 L 3.7778,5.5928 H 0 Z M 12.75,8.5 c 0,-1.7155 -0.9633,-3.1887 -2.3611,-3.9059 v 7.8021 C 11.7867,11.6887 12.75,10.2155 12.75,8.5 Z M 10.3889,0 v 1.9966 c 2.7294,0.83352 4.7222,3.431 4.7222,6.5034 0,3.0724 -1.9928,5.6699 -4.7222,6.5034 V 17 C 14.1761,16.118 17,12.6482 17,8.5 17,4.3518 14.1761,0.882 10.3889,0 Z"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					        <path
 | 
				
			||||||
 | 
					            id="i"
 | 
				
			||||||
 | 
					            d="m 12.75,8.5 c 0,-1.6717 -0.9633,-3.1072 -2.3611,-3.8061 V 6.7811 L 12.7028,9.095 C 12.7311,8.90611 12.75,8.70778 12.75,8.5 Z m 2.3611,0 c 0,0.88778 -0.1889,1.7189 -0.51,2.4933 l 1.4261,1.4261 C 16.6506,11.2483 17,9.9167 17,8.5 17,4.4578 14.1761,1.0767 10.3889,0.2172 V 2.1628 C 13.1183,2.97502 15.1111,5.5061 15.1111,8.5 Z M 1.1991,0 -3e-4,1.1994 4.4669,5.6666 H -3e-4 v 5.6666 h 3.7778 l 4.7222,4.7223 V 9.6993 l 4.0139,4.0139 c -0.6328,0.4911 -1.3411,0.8784 -2.125,1.1145 v 1.9455 c 1.3033,-0.2927 2.4839,-0.8972 3.485,-1.7094 l 1.9267,1.9361 1.1994,-1.1994 -8.5,-8.5 L 1.1991,-1e-4 Z m 7.3006,0.94444 -1.9739,1.9739 1.9739,1.9739 z"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					        <path
 | 
				
			||||||
 | 
					            id="h"
 | 
				
			||||||
 | 
					            d="M 12.444,0 H 1.555 C 0.69166,0 -6e-4,0.7 -6e-4,1.5556 v 10.889 c 0,0.8556 0.69222,1.5556 1.5556,1.5556 h 10.889 c 0.8556,0 1.5556,-0.7 1.5556,-1.5556 V 1.5556 C 13.9996,0.70004 13.2996,0 12.444,0 Z M 6.2218,9.3333 H 5.0551 V 7.7777 H 3.4995 V 9.3333 H 2.3328 V 4.6666 H 3.4995 V 6.611 H 5.0551 V 4.6666 H 6.2218 Z M 7.7774,4.6666 h 3.1111 c 0.4278,0 0.7778,0.35 0.7778,0.77777 v 3.1111 c 0,0.42777 -0.35,0.77777 -0.7778,0.77777 H 7.7774 v -4.6667 z m 1.1667,3.5 h 1.5556 V 5.8333 H 8.9441 Z"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					        <path
 | 
				
			||||||
 | 
					            id="g"
 | 
				
			||||||
 | 
					            d="M 0,0 V 17 L 13,8.5 Z"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					        <path
 | 
				
			||||||
 | 
					            id="f"
 | 
				
			||||||
 | 
					            d="M 0,17 H 4.3333 V 0 H 0 Z M 8.6667,0 V 17 H 13 V 0 Z"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					        <path
 | 
				
			||||||
 | 
					            id="e"
 | 
				
			||||||
 | 
					            d="m 0,11 h 3 v 3 H 5 V 9 H 0 Z M 3,3 H 0 V 5 H 5 V 0 H 3 Z m 6,11 h 2 v -3 h 3 V 9 H 9 Z M 11,3 V 0 H 9 v 5 h 5 V 3 Z"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					        <path
 | 
				
			||||||
 | 
					            id="d"
 | 
				
			||||||
 | 
					            d="M 0,12 8.5,6 0,0 Z M 10,0 v 12 h 2 V 0 Z"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					        <path
 | 
				
			||||||
 | 
					            id="c"
 | 
				
			||||||
 | 
					            d="M 1.52,4.5 C 1.52,2.961 2.632,1.71 4,1.71 H 7.2 V 0 H 4 C 1.792,0 0,2.016 0,4.5 0,6.984 1.792,9 4,9 H 7.2 V 7.29 H 4 C 2.632,7.29 1.52,6.039 1.52,4.5 Z M 4.8,5.4 h 6.4 V 3.6 H 4.8 Z M 12,0 H 8.8 V 1.71 H 12 c 1.368,0 2.48,1.251 2.48,2.79 0,1.539 -1.112,2.79 -2.48,2.79 H 8.8 V 9 H 12 C 14.208,9 16,6.984 16,4.5 16,2.016 14.208,0 12,0 Z"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					        <path
 | 
				
			||||||
 | 
					            id="b"
 | 
				
			||||||
 | 
					            d="M 2,9 H 0 v 5 H 5 V 12 H 2 Z M 0,5 H 2 V 2 H 5 V 0 H 0 Z m 12,7 H 9 v 2 h 5 V 9 H 12 Z M 9,0 v 2 h 3 v 3 h 2 V 0 Z"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					        <path
 | 
				
			||||||
 | 
					            id="a"
 | 
				
			||||||
 | 
					            d="M 4.4546,8.7015 1.1137,5.2537 1e-4,6.403 4.4546,11 14,1.1492 12.8864,-1e-4 4.4546,8.7014 Z"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					        <path
 | 
				
			||||||
 | 
					            id="n"
 | 
				
			||||||
 | 
					            d="M 12.348,7.686 C 12.3768,7.462 12.3984,7.238 12.3984,7 12.3984,6.762 12.3768,6.538 12.348,6.314 L 13.8664,5.159 C 14.0032,5.054 14.0391,4.865 13.9528,4.711 L 12.5135,2.289 C 12.4272,2.135 12.2329,2.079 12.0745,2.135 l -1.7919,0.7 C 9.90842,2.555 9.50543,2.324 9.0664,2.149 L 8.79294,0.294 C 8.77135,0.126 8.62022,0 8.44031,0 H 5.56171 C 5.38181,0 5.23068,0.126 5.20909,0.294 L 4.93563,2.149 C 4.49665,2.324 4.09365,2.562 3.71943,2.835 l -1.7919,-0.7 C 1.76201,2.072 1.5749,2.135 1.48855,2.289 l -1.4393,2.422 c -0.093553,0.154 -0.050374,0.343 0.086356,0.448 l 1.5184,1.155 C 1.625226,6.538 1.603636,6.769 1.603636,7 c 0,0.231 0.02159,0.462 0.05037,0.686 l -1.5184,1.155 c -0.13673,0.105 -0.17271,0.294 -0.086356,0.448 l 1.4393,2.422 c 0.08635,0.154 0.28066,0.21 0.43898,0.154 l 1.7919,-0.7 c 0.37421,0.28 0.77721,0.511 1.2162,0.686 l 0.27346,1.855 C 5.23068,13.874 5.38181,14 5.56171,14 h 2.8786 c 0.17991,0 0.33104,-0.126 0.35263,-0.294 L 9.0664,11.851 c 0.43898,-0.175 0.84197,-0.413 1.2162,-0.686 l 1.7919,0.7 c 0.1655,0.063 0.3527,0 0.439,-0.154 L 13.9528,9.289 C 14.0391,9.135 14.0032,8.946 13.8664,8.841 Z M 7.0011,9.45 C 5.6122,9.45 4.4824,8.351 4.4824,7 4.4824,5.649 5.6122,4.55 7.0011,4.55 8.39,4.55 9.5198,5.649 9.5198,7 9.5198,8.351 8.39,9.45 7.0011,9.45 Z"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					        <path
 | 
				
			||||||
 | 
					            id="m"
 | 
				
			||||||
 | 
					            d="M 0,0 H 16 V 12 H 0 Z"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					        <path
 | 
				
			||||||
 | 
					            id="l"
 | 
				
			||||||
 | 
					            d="m 0,0 v -2 h -2 v 2 z m 16,0 h 2 v -2 h -2 z m 0,12 v 2 h 2 V 12 Z M 0,12 h -2 v 2 H 0 Z M 0,2 H 16 V -2 H 0 Z M 14,0 v 12 h 4 V 0 Z m 2,10 H 0 v 4 H 16 Z M 2,12 V 0 h -4 v 12 z"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					        <path
 | 
				
			||||||
 | 
					            id="k"
 | 
				
			||||||
 | 
					            d="M 12,5.2941 H 8.5714 V 0 H 3.4285 V 5.2941 H -1e-4 l 6,6.1765 6,-6.1765 z M 0,13.2353 V 15 h 12 v -1.7647 z"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					        <path
 | 
				
			||||||
 | 
					            id="j"
 | 
				
			||||||
 | 
					            d="M 9.3333,0 H 4.6666 V 1.5238 H 9.3333 Z M 6.2222,9.9048 H 7.7778 V 5.3334 H 6.2222 Z M 12.4678,4.8686 13.5722,3.7867 C 13.2378,3.39813 12.8722,3.03241 12.4756,2.7124 L 11.3711,3.7943 C 10.1656,2.84953 8.6489,2.2857 7,2.2857 3.1344,2.2857 0,5.3562 0,9.1429 0,12.9295 3.1267,16 7,16 10.8733,16 14,12.9295 14,9.1429 14,7.5277 13.4244,6.0419 12.4678,4.8686 Z M 7,14.4762 C 3.99,14.4762 1.5556,12.0914 1.5556,9.1429 1.5556,6.1943 3.99,3.8096 7,3.8096 c 3.01,0 5.4444,2.3848 5.4444,5.3333 0,2.9485 -2.4344,5.3333 -5.4444,5.3333 z"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					    </defs>
 | 
				
			||||||
 | 
					    <g
 | 
				
			||||||
 | 
					        id="g3989"
 | 
				
			||||||
 | 
					        transform="matrix(0.03107656,0,0,0.03432918,271.52581,57.128037)"
 | 
				
			||||||
 | 
					        style="fill:#ffffff">
 | 
				
			||||||
 | 
					        <g
 | 
				
			||||||
 | 
					            id="g3979"
 | 
				
			||||||
 | 
					            style="fill:#ffffff">
 | 
				
			||||||
 | 
					            <path
 | 
				
			||||||
 | 
					                style="fill:#ffffff;stroke-width:0.03266241"
 | 
				
			||||||
 | 
					                d="m 273.49023,60.4375 c -1.07158,0 -1.96484,0.958273 -1.96484,2.169922 v 6.61914 c 0,1.216352 0.89727,2.167935 1.96484,2.167969 h 2.87305 c 0.52417,0 1.01765,-0.2246 1.39063,-0.634765 l 1,-1.103516 c 0.19059,-0.211639 0.45448,-0.333984 0.72656,-0.333984 0.27207,0 0.53829,0.122547 0.73047,0.335937 l 0.99804,1.101563 c 0.37212,0.409203 0.86646,0.634765 1.39063,0.634765 h 2.87305 c 1.07158,0 1.96484,-0.95632 1.96484,-2.167969 v -6.61914 c 0,-1.216352 -0.89727,-2.169922 -1.96484,-2.169922 z m 2.4961,3.308594 c 1.08295,0 1.96484,0.973618 1.96484,2.169922 0,1.196303 -0.88185,2.169921 -1.96484,2.169922 -1.08299,0 -1.96485,-0.973619 -1.96485,-2.169922 0,-1.196304 0.88189,-2.169922 1.96485,-2.169922 z m 6.99023,0 c 1.08296,0 1.96485,0.973618 1.96485,2.169922 0,1.196303 -0.88189,2.169922 -1.96485,2.169922 -1.08298,0 -1.96484,-0.973619 -1.96484,-2.169922 0,-1.196304 0.88189,-2.169922 1.96484,-2.169922 z"
 | 
				
			||||||
 | 
					                transform="matrix(32.178594,0,0,29.129737,-8737.3187,-1664.1247)"
 | 
				
			||||||
 | 
					                id="path3977"
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					        </g>
 | 
				
			||||||
 | 
					        <g
 | 
				
			||||||
 | 
					            id="g3983"
 | 
				
			||||||
 | 
					            style="fill:#ffffff"/>
 | 
				
			||||||
 | 
					        <g
 | 
				
			||||||
 | 
					            id="g3987"
 | 
				
			||||||
 | 
					            style="fill:#ffffff"/>
 | 
				
			||||||
 | 
					    </g>
 | 
				
			||||||
 | 
					    <path
 | 
				
			||||||
 | 
					        style="fill:#ffffff;fill-opacity:1"
 | 
				
			||||||
 | 
					        d="M 317.03125 21.992188 A 6.9826789 6.9826789 0 0 0 310.04883 28.976562 A 6.9826789 6.9826789 0 0 0 317.03125 35.958984 A 6.9826789 6.9826789 0 0 0 324.01367 28.976562 A 6.9826789 6.9826789 0 0 0 317.03125 21.992188 z M 312.20703 27.580078 L 321.94336 27.642578 C 322.32865 27.645013 322.63647 27.956519 322.63281 28.341797 L 322.62109 29.603516 C 322.61743 29.988794 322.30326 30.297356 321.91797 30.294922 L 312.18164 30.232422 C 311.79635 30.229987 311.48853 29.918481 311.49219 29.533203 L 311.50391 28.271484 C 311.50757 27.886206 311.82174 27.577644 312.20703 27.580078 z "
 | 
				
			||||||
 | 
					        id="path3997"/>
 | 
				
			||||||
 | 
					    <path
 | 
				
			||||||
 | 
					        style="fill:#ffffff;fill-opacity:1"
 | 
				
			||||||
 | 
					        d="M 317.03125 59.992188 A 6.9826789 6.9826789 0 0 0 310.04883 66.976562 A 6.9826789 6.9826789 0 0 0 317.03125 73.958984 A 6.9826789 6.9826789 0 0 0 324.01367 66.976562 A 6.9826789 6.9826789 0 0 0 317.03125 59.992188 z M 316.49805 61.365234 L 317.76172 61.382812 C 318.14697 61.388692 318.45192 61.704576 318.44727 62.089844 L 318.4043 65.619141 L 321.94336 65.642578 C 322.32865 65.645013 322.63647 65.956519 322.63281 66.341797 L 322.62109 67.603516 C 322.61743 67.988794 322.30326 68.297356 321.91797 68.294922 L 318.37305 68.271484 L 318.33008 71.826172 C 318.32543 72.21144 318.0122 72.515645 317.62695 72.509766 L 316.36328 72.492188 C 315.97803 72.486309 315.67308 72.170424 315.67773 71.785156 L 315.7207 68.255859 L 312.18164 68.232422 C 311.79635 68.229987 311.48853 67.918481 311.49219 67.533203 L 311.50391 66.271484 C 311.50757 65.886206 311.82174 65.577644 312.20703 65.580078 L 315.75195 65.603516 L 315.79492 62.048828 C 315.79957 61.66356 316.1128 61.359355 316.49805 61.365234 z "
 | 
				
			||||||
 | 
					        id="path3997-6"/>
 | 
				
			||||||
 | 
					    <ellipse
 | 
				
			||||||
 | 
					        style="fill:#800000;fill-opacity:1;stroke-width:1.10310566"
 | 
				
			||||||
 | 
					        id="circle4073"
 | 
				
			||||||
 | 
					        cx="353.64172"
 | 
				
			||||||
 | 
					        cy="-28.97954"
 | 
				
			||||||
 | 
					        transform="scale(1,-1)"
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					    <g
 | 
				
			||||||
 | 
					        id="g7787"
 | 
				
			||||||
 | 
					        transform="matrix(-0.02885349,0,0,-0.02885337,352.04486,75.172258)"
 | 
				
			||||||
 | 
					        style="fill:#ffffff;fill-opacity:1">
 | 
				
			||||||
 | 
					        <g
 | 
				
			||||||
 | 
					            id="g7731"
 | 
				
			||||||
 | 
					            transform="translate(-90.566882,42.049084)"
 | 
				
			||||||
 | 
					            style="fill:#ffffff;fill-opacity:1">
 | 
				
			||||||
 | 
					            <path
 | 
				
			||||||
 | 
					                id="path7729"
 | 
				
			||||||
 | 
					                d="M 242.607,0 C 108.629,0 0.001,108.628 0.001,242.606 c 0,133.976 108.628,242.606 242.606,242.606 133.978,0 242.604,-108.631 242.604,-242.606 C 485.212,108.628 376.585,0 242.607,0 Z M 401.815,288.094 H 219.862 v 90.979 L 83.397,242.606 219.862,106.141 v 90.978 h 181.953 z"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                style="fill:#ffffff;fill-opacity:1"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        </g>
 | 
				
			||||||
 | 
					        <g
 | 
				
			||||||
 | 
					            id="g7733"
 | 
				
			||||||
 | 
					            style="fill:#ffffff;fill-opacity:1">
 | 
				
			||||||
 | 
					        </g>
 | 
				
			||||||
 | 
					        <g
 | 
				
			||||||
 | 
					            id="g7735"
 | 
				
			||||||
 | 
					            style="fill:#ffffff;fill-opacity:1">
 | 
				
			||||||
 | 
					        </g>
 | 
				
			||||||
 | 
					        <g
 | 
				
			||||||
 | 
					            id="g7737"
 | 
				
			||||||
 | 
					            style="fill:#ffffff;fill-opacity:1">
 | 
				
			||||||
 | 
					        </g>
 | 
				
			||||||
 | 
					        <g
 | 
				
			||||||
 | 
					            id="g7739"
 | 
				
			||||||
 | 
					            style="fill:#ffffff;fill-opacity:1">
 | 
				
			||||||
 | 
					        </g>
 | 
				
			||||||
 | 
					        <g
 | 
				
			||||||
 | 
					            id="g7741"
 | 
				
			||||||
 | 
					            style="fill:#ffffff;fill-opacity:1">
 | 
				
			||||||
 | 
					        </g>
 | 
				
			||||||
 | 
					        <g
 | 
				
			||||||
 | 
					            id="g7743"
 | 
				
			||||||
 | 
					            style="fill:#ffffff;fill-opacity:1">
 | 
				
			||||||
 | 
					        </g>
 | 
				
			||||||
 | 
					        <g
 | 
				
			||||||
 | 
					            id="g7745"
 | 
				
			||||||
 | 
					            style="fill:#ffffff;fill-opacity:1">
 | 
				
			||||||
 | 
					        </g>
 | 
				
			||||||
 | 
					        <g
 | 
				
			||||||
 | 
					            id="g7747"
 | 
				
			||||||
 | 
					            style="fill:#ffffff;fill-opacity:1">
 | 
				
			||||||
 | 
					        </g>
 | 
				
			||||||
 | 
					        <g
 | 
				
			||||||
 | 
					            id="g7749"
 | 
				
			||||||
 | 
					            style="fill:#ffffff;fill-opacity:1">
 | 
				
			||||||
 | 
					        </g>
 | 
				
			||||||
 | 
					        <g
 | 
				
			||||||
 | 
					            id="g7751"
 | 
				
			||||||
 | 
					            style="fill:#ffffff;fill-opacity:1">
 | 
				
			||||||
 | 
					        </g>
 | 
				
			||||||
 | 
					        <g
 | 
				
			||||||
 | 
					            id="g7753"
 | 
				
			||||||
 | 
					            style="fill:#ffffff;fill-opacity:1">
 | 
				
			||||||
 | 
					        </g>
 | 
				
			||||||
 | 
					        <g
 | 
				
			||||||
 | 
					            id="g7755"
 | 
				
			||||||
 | 
					            style="fill:#ffffff;fill-opacity:1">
 | 
				
			||||||
 | 
					        </g>
 | 
				
			||||||
 | 
					        <g
 | 
				
			||||||
 | 
					            id="g7757"
 | 
				
			||||||
 | 
					            style="fill:#ffffff;fill-opacity:1">
 | 
				
			||||||
 | 
					        </g>
 | 
				
			||||||
 | 
					        <g
 | 
				
			||||||
 | 
					            id="g7759"
 | 
				
			||||||
 | 
					            style="fill:#ffffff;fill-opacity:1">
 | 
				
			||||||
 | 
					        </g>
 | 
				
			||||||
 | 
					        <g
 | 
				
			||||||
 | 
					            id="g7761"
 | 
				
			||||||
 | 
					            style="fill:#ffffff;fill-opacity:1">
 | 
				
			||||||
 | 
					        </g>
 | 
				
			||||||
 | 
					    </g>
 | 
				
			||||||
 | 
					</svg>
 | 
				
			||||||
| 
		 After Width: | Height: | Size: 18 KiB  | 
							
								
								
									
										7
									
								
								client/fluid-player/src/static/fluid-spinner.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,7 @@
 | 
				
			||||||
 | 
					<svg class="lds-eclipse" width="200" height="200" style="background:0 0" preserveAspectRatio="xMidYMid"
 | 
				
			||||||
 | 
					     viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
 | 
				
			||||||
 | 
					    <path d="M68.095 59.578A20 20 0 0031.14 44.27a22 20-67.5 0136.955 15.308" fill="#fff">
 | 
				
			||||||
 | 
					        <animateTransform attributeName="transform" begin="0s" calcMode="linear" dur="0.8s" keyTimes="0;1"
 | 
				
			||||||
 | 
					                          repeatCount="indefinite" type="rotate" values="0 50 51;360 50 51"/>
 | 
				
			||||||
 | 
					    </path>
 | 
				
			||||||
 | 
					</svg>
 | 
				
			||||||
| 
		 After Width: | Height: | Size: 478 B  | 
							
								
								
									
										3
									
								
								client/fluid-player/src/static/miniplayer-toggle-off.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,3 @@
 | 
				
			||||||
 | 
					<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
 | 
				
			||||||
 | 
					<path d="M3.5 20C3.1 20 2.75 19.85 2.45 19.55C2.15 19.25 2 18.9 2 18.5V11H3.5V18.5H20.5V5.5H11V4H20.5C20.9 4 21.25 4.15 21.55 4.45C21.85 4.75 22 5.1 22 5.5V18.5C22 18.9 21.85 19.25 21.55 19.55C21.25 19.85 20.9 20 20.5 20H3.5ZM17.425 16.5L18.5 15.425L14.725 11.675H17.675V10.175H12.175V15.675H13.675V12.75L17.425 16.5ZM2 9.5V4H9.5V9.5H2Z" fill="white"/>
 | 
				
			||||||
 | 
					</svg>
 | 
				
			||||||
| 
		 After Width: | Height: | Size: 456 B  | 
							
								
								
									
										3
									
								
								client/fluid-player/src/static/miniplayer-toggle-on.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,3 @@
 | 
				
			||||||
 | 
					<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
 | 
				
			||||||
 | 
					    <path d="M2.00005 11V9H5.60005L1.30005 4.7L2.70005 3.3L7.00005 7.6V4H9.00005V11H2.00005ZM4.00005 20C3.45005 20 2.97922 19.8042 2.58755 19.4125C2.19588 19.0208 2.00005 18.55 2.00005 18V13H4.00005V18H12V20H4.00005ZM20 13V6H11V4H20C20.55 4 21.0209 4.19583 21.4125 4.5875C21.8042 4.97917 22 5.45 22 6V13H20ZM14 20V15H22V20H14Z" fill="white"/>
 | 
				
			||||||
 | 
					</svg>
 | 
				
			||||||
| 
		 After Width: | Height: | Size: 446 B  | 
							
								
								
									
										5
									
								
								client/fluid-player/src/static/skip-backward.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,5 @@
 | 
				
			||||||
 | 
					<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white" width="24px" height="24px">
 | 
				
			||||||
 | 
					    <path d="M0 0h24v24H0V0z" fill="none"/>
 | 
				
			||||||
 | 
					    <path
 | 
				
			||||||
 | 
					        d="M11.99 5V1l-5 5 5 5V7c3.31 0 6 2.69 6 6s-2.69 6-6 6-6-2.69-6-6h-2c0 4.42 3.58 8 8 8s8-3.58 8-8-3.58-8-8-8zm-1.1 11h-.85v-3.26l-1.01.31v-.69l1.77-.63h.09V16zm4.28-1.76c0 .32-.03.6-.1.82s-.17.42-.29.57-.28.26-.45.33-.37.1-.59.1-.41-.03-.59-.1-.33-.18-.46-.33-.23-.34-.3-.57-.11-.5-.11-.82v-.74c0-.32.03-.6.1-.82s.17-.42.29-.57.28-.26.45-.33.37-.1.59-.1.41.03.59.1.33.18.46.33.23.34.3.57.11.5.11.82v.74zm-.85-.86c0-.19-.01-.35-.04-.48s-.07-.23-.12-.31-.11-.14-.19-.17-.16-.05-.25-.05-.18.02-.25.05-.14.09-.19.17-.09.18-.12.31-.04.29-.04.48v.97c0 .19.01.35.04.48s.07.24.12.32.11.14.19.17.16.05.25.05.18-.02.25-.05.14-.09.19-.17.09-.19.11-.32.04-.29.04-.48v-.97z"/>
 | 
				
			||||||
 | 
					</svg>
 | 
				
			||||||
| 
		 After Width: | Height: | Size: 834 B  | 
							
								
								
									
										18
									
								
								client/fluid-player/src/static/skip-forward.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,18 @@
 | 
				
			||||||
 | 
					<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" viewBox="0 0 24 24" fill="white" width="24px"
 | 
				
			||||||
 | 
					     height="24px">
 | 
				
			||||||
 | 
					    <g>
 | 
				
			||||||
 | 
					        <rect fill="none" height="24" width="24"/>
 | 
				
			||||||
 | 
					        <rect fill="none" height="24" width="24"/>
 | 
				
			||||||
 | 
					        <rect fill="none" height="24" width="24"/>
 | 
				
			||||||
 | 
					    </g>
 | 
				
			||||||
 | 
					    <g>
 | 
				
			||||||
 | 
					        <g/>
 | 
				
			||||||
 | 
					        <g>
 | 
				
			||||||
 | 
					            <path
 | 
				
			||||||
 | 
					                d="M18,13c0,3.31-2.69,6-6,6s-6-2.69-6-6s2.69-6,6-6v4l5-5l-5-5v4c-4.42,0-8,3.58-8,8c0,4.42,3.58,8,8,8s8-3.58,8-8H18z"/>
 | 
				
			||||||
 | 
					            <polygon points="10.9,16 10.9,11.73 10.81,11.73 9.04,12.36 9.04,13.05 10.05,12.74 10.05,16"/>
 | 
				
			||||||
 | 
					            <path
 | 
				
			||||||
 | 
					                d="M14.32,11.78c-0.18-0.07-0.37-0.1-0.59-0.1s-0.41,0.03-0.59,0.1s-0.33,0.18-0.45,0.33s-0.23,0.34-0.29,0.57 s-0.1,0.5-0.1,0.82v0.74c0,0.32,0.04,0.6,0.11,0.82s0.17,0.42,0.3,0.57s0.28,0.26,0.46,0.33s0.37,0.1,0.59,0.1s0.41-0.03,0.59-0.1 s0.33-0.18,0.45-0.33s0.22-0.34,0.29-0.57s0.1-0.5,0.1-0.82V13.5c0-0.32-0.04-0.6-0.11-0.82s-0.17-0.42-0.3-0.57 S14.49,11.85,14.32,11.78z M14.33,14.35c0,0.19-0.01,0.35-0.04,0.48s-0.06,0.24-0.11,0.32s-0.11,0.14-0.19,0.17 s-0.16,0.05-0.25,0.05s-0.18-0.02-0.25-0.05s-0.14-0.09-0.19-0.17s-0.09-0.19-0.12-0.32s-0.04-0.29-0.04-0.48v-0.97 c0-0.19,0.01-0.35,0.04-0.48s0.06-0.23,0.12-0.31s0.11-0.14,0.19-0.17s0.16-0.05,0.25-0.05s0.18,0.02,0.25,0.05 s0.14,0.09,0.19,0.17s0.09,0.18,0.12,0.31s0.04,0.29,0.04,0.48V14.35z"/>
 | 
				
			||||||
 | 
					        </g>
 | 
				
			||||||
 | 
					    </g>
 | 
				
			||||||
 | 
					</svg>
 | 
				
			||||||
| 
		 After Width: | Height: | Size: 1.4 KiB  | 
							
								
								
									
										546
									
								
								client/fluid-player/src/types.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,546 @@
 | 
				
			||||||
 | 
					export interface IFluidPlayer {
 | 
				
			||||||
 | 
					    domRef: DomRef;
 | 
				
			||||||
 | 
					    version: string;
 | 
				
			||||||
 | 
					    homepage: string;
 | 
				
			||||||
 | 
					    destructors: ((this: IFluidPlayer) => void)[];
 | 
				
			||||||
 | 
					    init: (this: IFluidPlayer, playerTarget: HTMLVideoElement | string, options: unknown) => void;
 | 
				
			||||||
 | 
					    getCurrentVideoDuration: () => number;
 | 
				
			||||||
 | 
					    getCurrentTime: () => number;
 | 
				
			||||||
 | 
					    toggleLoader: (showLoader?: boolean) => void;
 | 
				
			||||||
 | 
					    sendRequest: (
 | 
				
			||||||
 | 
					        url: unknown,
 | 
				
			||||||
 | 
					        withCredentials: unknown,
 | 
				
			||||||
 | 
					        timeout: unknown,
 | 
				
			||||||
 | 
					        functionReadyStateChange: unknown
 | 
				
			||||||
 | 
					    ) => void;
 | 
				
			||||||
 | 
					    displayOptions: DisplayOptions;
 | 
				
			||||||
 | 
					    sendRequestAsync: (
 | 
				
			||||||
 | 
					        url: unknown,
 | 
				
			||||||
 | 
					        withCredentials: unknown,
 | 
				
			||||||
 | 
					        timeout: unknown
 | 
				
			||||||
 | 
					    ) => Promise<unknown>;
 | 
				
			||||||
 | 
					    announceLocalError: (code: unknown, msg: unknown) => void;
 | 
				
			||||||
 | 
					    debugMessage: (...msg: unknown[]) => void;
 | 
				
			||||||
 | 
					    onMainVideoEnded: (event: unknown) => void;
 | 
				
			||||||
 | 
					    isCurrentlyPlayingAd: unknown;
 | 
				
			||||||
 | 
					    autoplayAfterAd: boolean;
 | 
				
			||||||
 | 
					    mainVideoDuration: unknown;
 | 
				
			||||||
 | 
					    adKeytimePlay: (keyTime: unknown) => void;
 | 
				
			||||||
 | 
					    timer: number | null;
 | 
				
			||||||
 | 
					    switchToMainVideo: () => void;
 | 
				
			||||||
 | 
					    playPauseToggle: () => void;
 | 
				
			||||||
 | 
					    displaySuggestedVideos: () => void;
 | 
				
			||||||
 | 
					    mainVideoCurrentTime: unknown;
 | 
				
			||||||
 | 
					    getCurrentSrc: () => null | string;
 | 
				
			||||||
 | 
					    getCurrentSrcType: () => null | string;
 | 
				
			||||||
 | 
					    onRecentWaiting: () => void;
 | 
				
			||||||
 | 
					    recentWaiting: boolean;
 | 
				
			||||||
 | 
					    onFluidPlayerPause: () => void;
 | 
				
			||||||
 | 
					    checkShouldDisplayVolumeBar: () => boolean;
 | 
				
			||||||
 | 
					    getMobileOs: () => { userOS: string };
 | 
				
			||||||
 | 
					    generateCustomControlTags: (options: unknown) => CustomControls;
 | 
				
			||||||
 | 
					    detectLiveStream: () => void;
 | 
				
			||||||
 | 
					    isLiveStream: () => boolean;
 | 
				
			||||||
 | 
					    showLiveIndicator: () => void;
 | 
				
			||||||
 | 
					    currentVideoDuration: number;
 | 
				
			||||||
 | 
					    controlPlayPauseToggle: () => void;
 | 
				
			||||||
 | 
					    playPauseAnimationToggle: (play: any) => void;
 | 
				
			||||||
 | 
					    isSwitchingSource: boolean;
 | 
				
			||||||
 | 
					    contolProgressbarUpdate: () => void;
 | 
				
			||||||
 | 
					    controlDurationUpdate: () => void;
 | 
				
			||||||
 | 
					    formatTime: (time: number) => void;
 | 
				
			||||||
 | 
					    hlsPlayer: false | HLSPlayer;
 | 
				
			||||||
 | 
					    contolVolumebarUpdate: () => void;
 | 
				
			||||||
 | 
					    latestVolume: number;
 | 
				
			||||||
 | 
					    fluidStorage: { fluidMute?: boolean; fluidVolume?: unknown };
 | 
				
			||||||
 | 
					    muteToggle: () => void;
 | 
				
			||||||
 | 
					    checkFullscreenSupport: () =>
 | 
				
			||||||
 | 
					        | false
 | 
				
			||||||
 | 
					        | {
 | 
				
			||||||
 | 
					              goFullscreen: string;
 | 
				
			||||||
 | 
					              exitFullscreen: string;
 | 
				
			||||||
 | 
					              isFullscreen: string;
 | 
				
			||||||
 | 
					          };
 | 
				
			||||||
 | 
					    fullscreenOff: (
 | 
				
			||||||
 | 
					        fullscreenButton: unknown[],
 | 
				
			||||||
 | 
					        menuOptionFullscreen: null
 | 
				
			||||||
 | 
					    ) => void;
 | 
				
			||||||
 | 
					    fullscreenMode: boolean;
 | 
				
			||||||
 | 
					    fullscreenOn: (
 | 
				
			||||||
 | 
					        fullscreenButton: unknown,
 | 
				
			||||||
 | 
					        menuOptionFullscreen: unknown
 | 
				
			||||||
 | 
					    ) => void;
 | 
				
			||||||
 | 
					    fullscreenToggle: () => void;
 | 
				
			||||||
 | 
					    findClosestParent: (el: unknown, selector: unknown) => null;
 | 
				
			||||||
 | 
					    getTranslateX: (el: unknown) => number;
 | 
				
			||||||
 | 
					    getEventOffsetX: (evt: unknown, el: unknown) => number;
 | 
				
			||||||
 | 
					    getEventOffsetY: (evt: unknown, el: unknown) => number;
 | 
				
			||||||
 | 
					    onProgressbarMouseDown: (event: unknown) => void;
 | 
				
			||||||
 | 
					    onVolumeBarMouseDown: () => void;
 | 
				
			||||||
 | 
					    findRoll: (roll: unknown) => string[] | undefined;
 | 
				
			||||||
 | 
					    onKeyboardVolumeChange: (direction: unknown) => void;
 | 
				
			||||||
 | 
					    onKeyboardSeekPosition: (keyCode: unknown) => void;
 | 
				
			||||||
 | 
					    getNewCurrentTimeValueByKeyCode: (
 | 
				
			||||||
 | 
					        keyCode: unknown,
 | 
				
			||||||
 | 
					        currentTime: unknown,
 | 
				
			||||||
 | 
					        duration: unknown
 | 
				
			||||||
 | 
					    ) => unknown;
 | 
				
			||||||
 | 
					    handleMouseleave: (event: unknown) => void;
 | 
				
			||||||
 | 
					    handleMouseenterForKeyboard: () => void;
 | 
				
			||||||
 | 
					    keyboardControl: () => void;
 | 
				
			||||||
 | 
					    handleWindowClick: (event: unknown) => void;
 | 
				
			||||||
 | 
					    initialPlay: () => void;
 | 
				
			||||||
 | 
					    setCustomControls: () => void;
 | 
				
			||||||
 | 
					    createTimePositionPreview: () => void;
 | 
				
			||||||
 | 
					    setCustomContextMenu: () => void;
 | 
				
			||||||
 | 
					    setDefaultLayout: () => void;
 | 
				
			||||||
 | 
					    initSkipControls: () => void;
 | 
				
			||||||
 | 
					    handleOrientationChange: () => void;
 | 
				
			||||||
 | 
					    initSkipAnimationElements: () => void;
 | 
				
			||||||
 | 
					    initDoubleTapSkip: () => void;
 | 
				
			||||||
 | 
					    skipRelative: (timeOffset: number) => void;
 | 
				
			||||||
 | 
					    checkIfVolumebarIsRendered: () => boolean;
 | 
				
			||||||
 | 
					    setLayout: () => void;
 | 
				
			||||||
 | 
					    handleFullscreen: () => void;
 | 
				
			||||||
 | 
					    setupPlayerWrapper: () => HTMLDivElement;
 | 
				
			||||||
 | 
					    onErrorDetection: () => void;
 | 
				
			||||||
 | 
					    createVideoSourceSwitch: (initialLoad?: boolean) => void;
 | 
				
			||||||
 | 
					    openCloseVideoSourceSwitch: () => void;
 | 
				
			||||||
 | 
					    setVideoSource: (url: unknown) => false | undefined;
 | 
				
			||||||
 | 
					    setCurrentTimeAndPlay: (
 | 
				
			||||||
 | 
					        newCurrentTime: unknown,
 | 
				
			||||||
 | 
					        shouldPlay: unknown
 | 
				
			||||||
 | 
					    ) => void;
 | 
				
			||||||
 | 
					    initTitle: () => void;
 | 
				
			||||||
 | 
					    hasTitle: () => unknown;
 | 
				
			||||||
 | 
					    hideTitle: () => void;
 | 
				
			||||||
 | 
					    showTitle: () => void;
 | 
				
			||||||
 | 
					    initLogo: () => void;
 | 
				
			||||||
 | 
					    initHtmlOnPauseBlock: () => void;
 | 
				
			||||||
 | 
					    initPlayButton: () => void;
 | 
				
			||||||
 | 
					    mainVideoReady: () => void;
 | 
				
			||||||
 | 
					    userActivityChecker: () => void;
 | 
				
			||||||
 | 
					    hasControlBar: () => boolean;
 | 
				
			||||||
 | 
					    isControlBarVisible: () => boolean;
 | 
				
			||||||
 | 
					    setVideoPreload: () => void;
 | 
				
			||||||
 | 
					    hideControlBar: () => void;
 | 
				
			||||||
 | 
					    showControlBar: (event: unknown) => void;
 | 
				
			||||||
 | 
					    linkControlBarUserActivity: () => void;
 | 
				
			||||||
 | 
					    initMute: () => void;
 | 
				
			||||||
 | 
					    initLoop: () => void;
 | 
				
			||||||
 | 
					    setBuffering: () => void;
 | 
				
			||||||
 | 
					    createPlaybackList: () => void;
 | 
				
			||||||
 | 
					    openCloseVideoPlaybackRate: () => void;
 | 
				
			||||||
 | 
					    createDownload: () => void;
 | 
				
			||||||
 | 
					    theatreToggle: () => void;
 | 
				
			||||||
 | 
					    defaultTheatre: () => void;
 | 
				
			||||||
 | 
					    posterImage: () => void;
 | 
				
			||||||
 | 
					    nextSource: () => null | undefined;
 | 
				
			||||||
 | 
					    inIframe: () => boolean;
 | 
				
			||||||
 | 
					    setPersistentSettings: (ignoreMute?: boolean) => false | undefined;
 | 
				
			||||||
 | 
					    play: () => true | undefined;
 | 
				
			||||||
 | 
					    pause: () => boolean;
 | 
				
			||||||
 | 
					    skipTo: (time: unknown) => void;
 | 
				
			||||||
 | 
					    setPlaybackSpeed: (speed: unknown) => void;
 | 
				
			||||||
 | 
					    setVolume: (passedVolume: unknown) => void;
 | 
				
			||||||
 | 
					    isCurrentlyPlayingVideo: (instance: HTMLVideoElement) => boolean;
 | 
				
			||||||
 | 
					    setHtmlOnPauseBlock: (passedHtml: unknown) => false | undefined;
 | 
				
			||||||
 | 
					    toggleControlBar: (show: unknown) => void;
 | 
				
			||||||
 | 
					    on: (eventCall: unknown, callback: unknown) => void;
 | 
				
			||||||
 | 
					    toggleLogo: (logo: unknown) => false | undefined;
 | 
				
			||||||
 | 
					    trackEvent: (
 | 
				
			||||||
 | 
					        el: unknown,
 | 
				
			||||||
 | 
					        evt: unknown,
 | 
				
			||||||
 | 
					        sel: unknown,
 | 
				
			||||||
 | 
					        handler: unknown
 | 
				
			||||||
 | 
					    ) => void;
 | 
				
			||||||
 | 
					    registerListener: (
 | 
				
			||||||
 | 
					        el: unknown,
 | 
				
			||||||
 | 
					        evt: unknown,
 | 
				
			||||||
 | 
					        sel: unknown,
 | 
				
			||||||
 | 
					        handler: unknown
 | 
				
			||||||
 | 
					    ) => void;
 | 
				
			||||||
 | 
					    copyEvents: (topLevelEl: unknown) => void;
 | 
				
			||||||
 | 
					    resetDisplayMode: (
 | 
				
			||||||
 | 
					        displayTarget: "fullScreen" | "theaterMode" | "miniPlayer"
 | 
				
			||||||
 | 
					    ) => void;
 | 
				
			||||||
 | 
					    destroy: () => void;
 | 
				
			||||||
 | 
					    setCTAFromVast: (titleCtaElement: HTMLElement, tmpOptions: unknown) => void;
 | 
				
			||||||
 | 
					    getClickThroughUrlFromLinear: (linear: HTMLElement) => unknown;
 | 
				
			||||||
 | 
					    getVastAdTagUriFromWrapper: (xmlResponse: HTMLElement) => unknown;
 | 
				
			||||||
 | 
					    hasInLine: (xmlResponse: HTMLElement) => number | false;
 | 
				
			||||||
 | 
					    hasVastAdTagUri: (xmlResponse: HTMLElement) => number | false;
 | 
				
			||||||
 | 
					    getClickThroughUrlFromNonLinear: (nonLinear: HTMLElement) => string;
 | 
				
			||||||
 | 
					    getTrackingFromLinear: (
 | 
				
			||||||
 | 
					        linear: HTMLElement
 | 
				
			||||||
 | 
					    ) => never[] | HTMLCollectionOf<Element>;
 | 
				
			||||||
 | 
					    getDurationFromLinear: (linear: HTMLElement) => unknown;
 | 
				
			||||||
 | 
					    getDurationFromNonLinear: (tag: HTMLElement) => number;
 | 
				
			||||||
 | 
					    getDimensionFromNonLinear: (tag: HTMLElement) => {
 | 
				
			||||||
 | 
					        width: null;
 | 
				
			||||||
 | 
					        height: null;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    getCreativeTypeFromStaticResources: (tag: HTMLElement) => string;
 | 
				
			||||||
 | 
					    getMediaFilesFromLinear: (
 | 
				
			||||||
 | 
					        linear: HTMLElement
 | 
				
			||||||
 | 
					    ) => HTMLCollectionOf<Element> | never[];
 | 
				
			||||||
 | 
					    getStaticResourcesFromNonLinear: (linear: HTMLElement) => unknown[];
 | 
				
			||||||
 | 
					    extractNodeDataByTagName: (
 | 
				
			||||||
 | 
					        parentNode: HTMLElement,
 | 
				
			||||||
 | 
					        tagName: string
 | 
				
			||||||
 | 
					    ) => string | null;
 | 
				
			||||||
 | 
					    extractNodeData: (parentNode: HTMLElement) => string;
 | 
				
			||||||
 | 
					    getAdParametersFromLinear: (linear: HTMLElement) => string | null;
 | 
				
			||||||
 | 
					    getMediaFileListFromLinear: (linear: HTMLElement) => unknown[];
 | 
				
			||||||
 | 
					    getIconClickThroughFromLinear: (linear: HTMLElement) => string;
 | 
				
			||||||
 | 
					    getStaticResourceFromNonLinear: (linear: HTMLElement) => string | undefined;
 | 
				
			||||||
 | 
					    registerTrackingEvents: (
 | 
				
			||||||
 | 
					        creativeLinear: unknown,
 | 
				
			||||||
 | 
					        tmpOptions: unknown
 | 
				
			||||||
 | 
					    ) => void;
 | 
				
			||||||
 | 
					    registerClickTracking: (
 | 
				
			||||||
 | 
					        clickTrackingTag: unknown,
 | 
				
			||||||
 | 
					        tmpOptions: unknown
 | 
				
			||||||
 | 
					    ) => void;
 | 
				
			||||||
 | 
					    registerViewableImpressionEvents: (
 | 
				
			||||||
 | 
					        viewableImpressionTags: unknown,
 | 
				
			||||||
 | 
					        tmpOptions: unknown
 | 
				
			||||||
 | 
					    ) => void;
 | 
				
			||||||
 | 
					    registerImpressionEvents: (
 | 
				
			||||||
 | 
					        impressionTags: unknown,
 | 
				
			||||||
 | 
					        tmpOptions: unknown
 | 
				
			||||||
 | 
					    ) => void;
 | 
				
			||||||
 | 
					    registerErrorEvents: (errorTags: unknown, tmpOptions: unknown) => void;
 | 
				
			||||||
 | 
					    announceError: (code: unknown) => void;
 | 
				
			||||||
 | 
					    getClickTrackingEvents: (linear: unknown) => string[] | undefined;
 | 
				
			||||||
 | 
					    getNonLinearClickTrackingEvents: (
 | 
				
			||||||
 | 
					        nonLinear: unknown
 | 
				
			||||||
 | 
					    ) => string[] | undefined;
 | 
				
			||||||
 | 
					    callUris: (uris: unknown) => void;
 | 
				
			||||||
 | 
					    recalculateAdDimensions: () => void;
 | 
				
			||||||
 | 
					    prepareVast: (roll: unknown) => void;
 | 
				
			||||||
 | 
					    playMainVideoWhenVastFails: (errorCode: unknown) => void;
 | 
				
			||||||
 | 
					    switchPlayerToVastMode: () => void;
 | 
				
			||||||
 | 
					    processVastWithRetries: (vastObj: unknown) => void;
 | 
				
			||||||
 | 
					    processUrl: (
 | 
				
			||||||
 | 
					        vastTag: unknown,
 | 
				
			||||||
 | 
					        callBack: unknown,
 | 
				
			||||||
 | 
					        rollListId: unknown
 | 
				
			||||||
 | 
					    ) => void;
 | 
				
			||||||
 | 
					    resolveVastTag: (
 | 
				
			||||||
 | 
					        vastTag: unknown,
 | 
				
			||||||
 | 
					        numberOfRedirects: unknown,
 | 
				
			||||||
 | 
					        tmpOptions: unknown,
 | 
				
			||||||
 | 
					        callback: unknown,
 | 
				
			||||||
 | 
					        rollListId: unknown
 | 
				
			||||||
 | 
					    ) => any;
 | 
				
			||||||
 | 
					    setVastList: () => void;
 | 
				
			||||||
 | 
					    onVastAdEnded: (event: unknown) => void;
 | 
				
			||||||
 | 
					    vastLogoBehaviour: (vastPlaying: unknown) => void;
 | 
				
			||||||
 | 
					    deleteVastAdElements: () => void;
 | 
				
			||||||
 | 
					    renderLinearAd: (ad: unknown, backupTheVideoTime: unknown) => void;
 | 
				
			||||||
 | 
					    playRoll: (adList: unknown) => void;
 | 
				
			||||||
 | 
					    backupMainVideoContentTime: (rollListId: unknown) => void;
 | 
				
			||||||
 | 
					    getSupportedMediaFileObject: (mediaFiles: unknown) => unknown;
 | 
				
			||||||
 | 
					    getMediaFileTypeSupportLevel: (
 | 
				
			||||||
 | 
					        mediaType: unknown
 | 
				
			||||||
 | 
					    ) => "maybe" | "probably" | "no" | null;
 | 
				
			||||||
 | 
					    scheduleTrackingEvent: (currentTime: unknown, duration: unknown) => void;
 | 
				
			||||||
 | 
					    trackSingleEvent: (eventType: unknown, eventSubType: unknown) => void;
 | 
				
			||||||
 | 
					    completeNonLinearStatic: (ad: unknown) => void;
 | 
				
			||||||
 | 
					    createNonLinearStatic: (ad: unknown) => void;
 | 
				
			||||||
 | 
					    createVpaidNonLinearBoard: (ad: unknown) => void;
 | 
				
			||||||
 | 
					    createNonLinearBoard: (ad: unknown) => void;
 | 
				
			||||||
 | 
					    createBoard: (ad: unknown) => void;
 | 
				
			||||||
 | 
					    closeNonLinear: (adId: unknown) => void;
 | 
				
			||||||
 | 
					    rollGroupContainsLinear: (groupedRolls: unknown) => boolean;
 | 
				
			||||||
 | 
					    rollGroupContainsNonlinear: (groupedRolls: unknown) => boolean;
 | 
				
			||||||
 | 
					    preRollFail: () => void;
 | 
				
			||||||
 | 
					    preRollSuccess: () => void;
 | 
				
			||||||
 | 
					    preRollAdsPlay: () => void;
 | 
				
			||||||
 | 
					    preRoll: (event: unknown) => void;
 | 
				
			||||||
 | 
					    createAdMarker: (adListId: unknown, time: unknown) => void;
 | 
				
			||||||
 | 
					    hideAdMarker: (adListId: unknown) => void;
 | 
				
			||||||
 | 
					    showAdMarkers: () => void;
 | 
				
			||||||
 | 
					    hideAdMarkers: () => void;
 | 
				
			||||||
 | 
					    midRoll: (event: unknown) => void;
 | 
				
			||||||
 | 
					    postRoll: (event: unknown) => void;
 | 
				
			||||||
 | 
					    onPauseRoll: (event: unknown) => void;
 | 
				
			||||||
 | 
					    hasValidOnPauseAd: () => unknown;
 | 
				
			||||||
 | 
					    toggleOnPauseAd: () => void;
 | 
				
			||||||
 | 
					    trackingOnPauseNonLinearAd: (ad: unknown, status: unknown) => void;
 | 
				
			||||||
 | 
					    getLinearAdsFromKeyTime: (keyTimeLinearObj: unknown) => unknown[];
 | 
				
			||||||
 | 
					    adTimer: () => void;
 | 
				
			||||||
 | 
					    scheduleTask: (task: {
 | 
				
			||||||
 | 
					        time: number;
 | 
				
			||||||
 | 
					        rollListId: unknown;
 | 
				
			||||||
 | 
					        loadVast: unknown;
 | 
				
			||||||
 | 
					    }) => void;
 | 
				
			||||||
 | 
					    scheduleOnDemandRolls: () => void;
 | 
				
			||||||
 | 
					    getNextAdPod: () => unknown;
 | 
				
			||||||
 | 
					    checkForNextAd: () => void;
 | 
				
			||||||
 | 
					    addSkipButton: () => void;
 | 
				
			||||||
 | 
					    addAdCountdown: () => void;
 | 
				
			||||||
 | 
					    decreaseAdCountdown: () => void;
 | 
				
			||||||
 | 
					    removeAdCountdown: () => void;
 | 
				
			||||||
 | 
					    toggleAdCountdown: (showing: unknown) => void;
 | 
				
			||||||
 | 
					    addAdPlayingText: (textToShow: unknown) => void;
 | 
				
			||||||
 | 
					    positionTextElements: (adListData: unknown) => void;
 | 
				
			||||||
 | 
					    removeAdPlayingText: () => void;
 | 
				
			||||||
 | 
					    addCTAButton: (landingPage: string) => unknown;
 | 
				
			||||||
 | 
					    createAndAppendCTAButton: (
 | 
				
			||||||
 | 
					        adCTAText: string,
 | 
				
			||||||
 | 
					        displayUrl: string,
 | 
				
			||||||
 | 
					        trackingUrl: string
 | 
				
			||||||
 | 
					    ) => void;
 | 
				
			||||||
 | 
					    removeCTAButton: () => void;
 | 
				
			||||||
 | 
					    decreaseSkipOffset: () => void;
 | 
				
			||||||
 | 
					    pressSkipButton: () => void;
 | 
				
			||||||
 | 
					    removeSkipButton: () => void;
 | 
				
			||||||
 | 
					    addClickthroughLayer: () => void;
 | 
				
			||||||
 | 
					    removeClickthrough: () => void;
 | 
				
			||||||
 | 
					    isTimer: unknown;
 | 
				
			||||||
 | 
					    vrROTATION_POSITION: number;
 | 
				
			||||||
 | 
					    vrROTATION_SPEED: number;
 | 
				
			||||||
 | 
					    vrMode: boolean;
 | 
				
			||||||
 | 
					    vrPanorama: null;
 | 
				
			||||||
 | 
					    vrViewer: null;
 | 
				
			||||||
 | 
					    vpaidTimer: null;
 | 
				
			||||||
 | 
					    vpaidAdUnit: null;
 | 
				
			||||||
 | 
					    vastOptions: null;
 | 
				
			||||||
 | 
					    videoPlayerId: string;
 | 
				
			||||||
 | 
					    originalSrc: string | null;
 | 
				
			||||||
 | 
					    firstPlayLaunched: boolean;
 | 
				
			||||||
 | 
					    suppressClickthrough: boolean;
 | 
				
			||||||
 | 
					    timelinePreviewData: unknown[];
 | 
				
			||||||
 | 
					    timerPool: Record<string, unknown>;
 | 
				
			||||||
 | 
					    rollsById: Record<string, unknown>;
 | 
				
			||||||
 | 
					    adPool: Record<string, unknown>;
 | 
				
			||||||
 | 
					    adGroupedByRolls: Record<string, unknown>;
 | 
				
			||||||
 | 
					    onPauseRollAdPods: unknown[];
 | 
				
			||||||
 | 
					    currentOnPauseRollAd: string;
 | 
				
			||||||
 | 
					    preRollAdsResolved: boolean;
 | 
				
			||||||
 | 
					    preRollAdPods: unknown[];
 | 
				
			||||||
 | 
					    preRollAdPodsLength: number;
 | 
				
			||||||
 | 
					    preRollVastResolved: number;
 | 
				
			||||||
 | 
					    temporaryAdPods: unknown[];
 | 
				
			||||||
 | 
					    availableRolls: ["preRoll", "midRoll", "postRoll", "onPauseRoll"];
 | 
				
			||||||
 | 
					    supportedNonLinearAd: ["300x250", "468x60", "728x90"];
 | 
				
			||||||
 | 
					    nonLinearDuration: number;
 | 
				
			||||||
 | 
					    supportedStaticTypes: ["image/gif", "image/jpeg", "image/png"];
 | 
				
			||||||
 | 
					    inactivityTimeout: null;
 | 
				
			||||||
 | 
					    isUserActive: null;
 | 
				
			||||||
 | 
					    nonLinearVerticalAlign: "bottom";
 | 
				
			||||||
 | 
					    vpaidNonLinearCloseButton: boolean;
 | 
				
			||||||
 | 
					    showTimeOnHover: boolean;
 | 
				
			||||||
 | 
					    initialAnimationSet: boolean;
 | 
				
			||||||
 | 
					    theatreMode: boolean;
 | 
				
			||||||
 | 
					    theatreModeAdvanced: boolean;
 | 
				
			||||||
 | 
					    originalWidth: number;
 | 
				
			||||||
 | 
					    originalHeight: number;
 | 
				
			||||||
 | 
					    dashPlayer: boolean;
 | 
				
			||||||
 | 
					    dashScriptLoaded: boolean;
 | 
				
			||||||
 | 
					    hlsScriptLoaded: boolean;
 | 
				
			||||||
 | 
					    isPlayingMedia: boolean;
 | 
				
			||||||
 | 
					    isLoading: boolean;
 | 
				
			||||||
 | 
					    isInIframe: boolean;
 | 
				
			||||||
 | 
					    mainVideoReadyState: boolean;
 | 
				
			||||||
 | 
					    xmlCollection: unknown[];
 | 
				
			||||||
 | 
					    inLineFound: null;
 | 
				
			||||||
 | 
					    fluidPseudoPause: boolean;
 | 
				
			||||||
 | 
					    mobileInfo: { userOS: string };
 | 
				
			||||||
 | 
					    events: Record<string, unknown>;
 | 
				
			||||||
 | 
					    timeSkipOffsetAmount: number;
 | 
				
			||||||
 | 
					    currentMediaSourceType: "source";
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface DomRef {
 | 
				
			||||||
 | 
					    player: null | HTMLVideoElement;
 | 
				
			||||||
 | 
					    wrapper?: null | HTMLDivElement;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface DisplayOptions {
 | 
				
			||||||
 | 
					    onBeforeXMLHttpRequestOpen: (request: XMLHttpRequest) => void;
 | 
				
			||||||
 | 
					    onBeforeXMLHttpRequest: (request: XMLHttpRequest) => void;
 | 
				
			||||||
 | 
					    debug: boolean;
 | 
				
			||||||
 | 
					    layoutControls: ILayoutControls;
 | 
				
			||||||
 | 
					    suggestedVideos: { configUrl: null };
 | 
				
			||||||
 | 
					    vastOptions: IVastOptions;
 | 
				
			||||||
 | 
					    captions: ICaptions;
 | 
				
			||||||
 | 
					    hls: IHLSOptions;
 | 
				
			||||||
 | 
					    modules: {
 | 
				
			||||||
 | 
					        configureHls: (options: unknown) => typeof options;
 | 
				
			||||||
 | 
					        onBeforeInitHls: (hls: unknown) => unknown;
 | 
				
			||||||
 | 
					        onAfterInitHls: (hls: unknown) => unknown;
 | 
				
			||||||
 | 
					        configureDash: (options: unknown) => typeof options;
 | 
				
			||||||
 | 
					        onBeforeInitDash: (dash: unknown) => void;
 | 
				
			||||||
 | 
					        onAfterInitDash: (dash: unknown) => void;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface ILayoutControls {
 | 
				
			||||||
 | 
					    mediaType: string | null;
 | 
				
			||||||
 | 
					    primaryColor: boolean;
 | 
				
			||||||
 | 
					    posterImage: boolean;
 | 
				
			||||||
 | 
					    posterImageSize: "contain";
 | 
				
			||||||
 | 
					    adProgressColor: string;
 | 
				
			||||||
 | 
					    playButtonShowing: boolean;
 | 
				
			||||||
 | 
					    playPauseAnimation: boolean;
 | 
				
			||||||
 | 
					    closeButtonCaption: "Close"; // Remove?
 | 
				
			||||||
 | 
					    fillToContainer: boolean;
 | 
				
			||||||
 | 
					    autoPlay: boolean;
 | 
				
			||||||
 | 
					    preload: "auto";
 | 
				
			||||||
 | 
					    mute: boolean;
 | 
				
			||||||
 | 
					    loop: null;
 | 
				
			||||||
 | 
					    keyboardControl: boolean;
 | 
				
			||||||
 | 
					    allowDownload: boolean;
 | 
				
			||||||
 | 
					    playbackRateEnabled: boolean;
 | 
				
			||||||
 | 
					    subtitlesEnabled: boolean;
 | 
				
			||||||
 | 
					    subtitlesOnByDefault: boolean;
 | 
				
			||||||
 | 
					    showCardBoardView: boolean;
 | 
				
			||||||
 | 
					    showCardBoardJoystick: boolean;
 | 
				
			||||||
 | 
					    allowTheatre: boolean;
 | 
				
			||||||
 | 
					    doubleclickFullscreen: boolean;
 | 
				
			||||||
 | 
					    autoRotateFullScreen: boolean;
 | 
				
			||||||
 | 
					    theatreSettings: ITheatreOptions;
 | 
				
			||||||
 | 
					    theatreAdvanced: {
 | 
				
			||||||
 | 
					        theatreElement: null;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    title: null;
 | 
				
			||||||
 | 
					    logo: ILogoOptions;
 | 
				
			||||||
 | 
					    controlBar: {
 | 
				
			||||||
 | 
					        autoHide: false;
 | 
				
			||||||
 | 
					        autoHideTimeout: 3;
 | 
				
			||||||
 | 
					        animated: true;
 | 
				
			||||||
 | 
					        playbackRates: ["x2", "x1.5", "x1", "x0.5"];
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    timelinePreview: {
 | 
				
			||||||
 | 
					        spriteImage: false;
 | 
				
			||||||
 | 
					        spriteRelativePath: false;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    htmlOnPauseBlock: {
 | 
				
			||||||
 | 
					        html: null;
 | 
				
			||||||
 | 
					        height: null;
 | 
				
			||||||
 | 
					        width: null;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    layout: "default"; //options: 'default', '<custom>'
 | 
				
			||||||
 | 
					    playerInitCallback: () => void;
 | 
				
			||||||
 | 
					    persistentSettings: {
 | 
				
			||||||
 | 
					        volume: true;
 | 
				
			||||||
 | 
					        quality: true;
 | 
				
			||||||
 | 
					        speed: true;
 | 
				
			||||||
 | 
					        theatre: true;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    controlForwardBackward: {
 | 
				
			||||||
 | 
					        show: false;
 | 
				
			||||||
 | 
					        doubleTapMobile: true;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    contextMenu: {
 | 
				
			||||||
 | 
					        controls: true;
 | 
				
			||||||
 | 
					        links: [];
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    miniPlayer: {
 | 
				
			||||||
 | 
					        enabled: true;
 | 
				
			||||||
 | 
					        width: 400;
 | 
				
			||||||
 | 
					        height: 225;
 | 
				
			||||||
 | 
					        widthMobile: 50;
 | 
				
			||||||
 | 
					        placeholderText: "Playing in Miniplayer";
 | 
				
			||||||
 | 
					        position: "bottom right";
 | 
				
			||||||
 | 
					        autoToggle: false;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    roundedCorners: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface ITheatreOptions {
 | 
				
			||||||
 | 
					    width: "100%";
 | 
				
			||||||
 | 
					    height: "60%";
 | 
				
			||||||
 | 
					    marginTop: number;
 | 
				
			||||||
 | 
					    horizontalAlign: "center";
 | 
				
			||||||
 | 
					    keepPosition: boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface ILogoOptions {
 | 
				
			||||||
 | 
					    imageUrl: null;
 | 
				
			||||||
 | 
					    position: "top left";
 | 
				
			||||||
 | 
					    clickUrl: null;
 | 
				
			||||||
 | 
					    opacity: 1;
 | 
				
			||||||
 | 
					    mouseOverImageUrl: null;
 | 
				
			||||||
 | 
					    imageMargin: "2px";
 | 
				
			||||||
 | 
					    hideWithControls: false;
 | 
				
			||||||
 | 
					    showOverAds: false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface ICaptions {
 | 
				
			||||||
 | 
					    play: "Play";
 | 
				
			||||||
 | 
					    pause: "Pause";
 | 
				
			||||||
 | 
					    mute: "Mute";
 | 
				
			||||||
 | 
					    unmute: "Unmute";
 | 
				
			||||||
 | 
					    fullscreen: "Fullscreen";
 | 
				
			||||||
 | 
					    subtitles: "Subtitles";
 | 
				
			||||||
 | 
					    exitFullscreen: "Exit Fullscreen";
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IVastOptions {
 | 
				
			||||||
 | 
					    adList: Record<string, unknown>;
 | 
				
			||||||
 | 
					    skipButtonCaption: "Skip ad in [seconds]";
 | 
				
			||||||
 | 
					    skipButtonClickCaption: 'Skip Ad <span class="skip_button_icon"></span>';
 | 
				
			||||||
 | 
					    adText: null;
 | 
				
			||||||
 | 
					    adTextPosition: "top left";
 | 
				
			||||||
 | 
					    adCTAText: "Visit now!";
 | 
				
			||||||
 | 
					    adCTATextPosition: "bottom right";
 | 
				
			||||||
 | 
					    adCTATextVast: boolean;
 | 
				
			||||||
 | 
					    adClickable: boolean;
 | 
				
			||||||
 | 
					    vastTimeout: number;
 | 
				
			||||||
 | 
					    showProgressbarMarkers: boolean;
 | 
				
			||||||
 | 
					    allowVPAID: boolean;
 | 
				
			||||||
 | 
					    showPlayButton: boolean;
 | 
				
			||||||
 | 
					    maxAllowedVastTagRedirects: number;
 | 
				
			||||||
 | 
					    vpaidTimeout: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    vastAdvanced: {
 | 
				
			||||||
 | 
					        vastLoadedCallback: () => void;
 | 
				
			||||||
 | 
					        noVastVideoCallback: () => void;
 | 
				
			||||||
 | 
					        vastVideoSkippedCallback: () => void;
 | 
				
			||||||
 | 
					        vastVideoEndedCallback: () => void;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface CustomControls {
 | 
				
			||||||
 | 
					    loader: HTMLDivElement;
 | 
				
			||||||
 | 
					    root: HTMLDivElement;
 | 
				
			||||||
 | 
					    leftContainer: HTMLDivElement;
 | 
				
			||||||
 | 
					    playPause: HTMLDivElement;
 | 
				
			||||||
 | 
					    skipBack: HTMLDivElement;
 | 
				
			||||||
 | 
					    skipForward: HTMLDivElement;
 | 
				
			||||||
 | 
					    progressContainer: HTMLDivElement;
 | 
				
			||||||
 | 
					    progressWrapper: HTMLDivElement;
 | 
				
			||||||
 | 
					    progressCurrent: HTMLDivElement;
 | 
				
			||||||
 | 
					    progress_current_marker: HTMLDivElement;
 | 
				
			||||||
 | 
					    bufferedIndicator: HTMLDivElement;
 | 
				
			||||||
 | 
					    adMarkers: HTMLDivElement;
 | 
				
			||||||
 | 
					    rightContainer: HTMLDivElement;
 | 
				
			||||||
 | 
					    fullscreen: HTMLDivElement;
 | 
				
			||||||
 | 
					    miniPlayer: HTMLDivElement;
 | 
				
			||||||
 | 
					    theatre: HTMLDivElement;
 | 
				
			||||||
 | 
					    cardboard: HTMLDivElement;
 | 
				
			||||||
 | 
					    subtitles: HTMLDivElement;
 | 
				
			||||||
 | 
					    videoSource: HTMLDivElement;
 | 
				
			||||||
 | 
					    playbackRate: HTMLDivElement;
 | 
				
			||||||
 | 
					    download: HTMLDivElement;
 | 
				
			||||||
 | 
					    volumeContainer: HTMLDivElement;
 | 
				
			||||||
 | 
					    volume: HTMLDivElement;
 | 
				
			||||||
 | 
					    volumeCurrent: HTMLDivElement;
 | 
				
			||||||
 | 
					    volumeCurrentPos: HTMLDivElement;
 | 
				
			||||||
 | 
					    mute: HTMLDivElement;
 | 
				
			||||||
 | 
					    durationContainer: HTMLDivElement;
 | 
				
			||||||
 | 
					    duration: HTMLDivElement;
 | 
				
			||||||
 | 
					    live_indicator: HTMLDivElement;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IHLSOptions {
 | 
				
			||||||
 | 
					    overrideNative: boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface HLSPlayer {
 | 
				
			||||||
 | 
					    levels: { details: { live: unknown } }[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										43
									
								
								client/fluid-player/test/html/custom_context.tpl.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,43 @@
 | 
				
			||||||
 | 
					<!doctype html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
 | 
					    <meta name="viewport"
 | 
				
			||||||
 | 
					          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
 | 
				
			||||||
 | 
					    <meta http-equiv="X-UA-Compatible" content="ie=edge">
 | 
				
			||||||
 | 
					    <title>Custom context menu</title>
 | 
				
			||||||
 | 
					    <%= htmlWebpackPlugin.tags.headTags %>
 | 
				
			||||||
 | 
					    <style lang="css">
 | 
				
			||||||
 | 
					        #fluid-player-e2e-case {
 | 
				
			||||||
 | 
					            width: 90%;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<video id="fluid-player-e2e-case">
 | 
				
			||||||
 | 
					    <source src="https://cdn.fluidplayer.com/videos/valerian-1080p.mkv" data-fluid-hd title="1080p" type="video/mp4"/>
 | 
				
			||||||
 | 
					    <source src="https://cdn.fluidplayer.com/videos/valerian-720p.mkv" data-fluid-hd title="720p" type="video/mp4"/>
 | 
				
			||||||
 | 
					    <source src="https://cdn.fluidplayer.com/videos/valerian-480p.mkv" title="480p" type="video/mp4"/>
 | 
				
			||||||
 | 
					</video>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<%= htmlWebpackPlugin.tags.bodyTags %>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    var instance = fluidPlayer('fluid-player-e2e-case', {
 | 
				
			||||||
 | 
					        layoutControls: {
 | 
				
			||||||
 | 
					            contextMenu: {
 | 
				
			||||||
 | 
					                controls: true,
 | 
				
			||||||
 | 
					                links: [
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        href: 'https://docs.fluidplayer.com',
 | 
				
			||||||
 | 
					                        label: 'Documentation'
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                ]
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										29
									
								
								client/fluid-player/test/html/dash_live.tpl.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,29 @@
 | 
				
			||||||
 | 
					<!doctype html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
 | 
					    <meta name="viewport"
 | 
				
			||||||
 | 
					          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
 | 
				
			||||||
 | 
					    <meta http-equiv="X-UA-Compatible" content="ie=edge">
 | 
				
			||||||
 | 
					    <title>DASH Live Stream</title>
 | 
				
			||||||
 | 
					    <%= htmlWebpackPlugin.tags.headTags %>
 | 
				
			||||||
 | 
					    <style lang="css">
 | 
				
			||||||
 | 
					        #fluid-player-e2e-case {
 | 
				
			||||||
 | 
					            width: 90%;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<video id="fluid-player-e2e-case">
 | 
				
			||||||
 | 
					    <source src="https://dash.akamaized.net/dash264/TestCasesIOP33/adapatationSetSwitching/5/manifest.mpd" type="application/dash+xml"/>
 | 
				
			||||||
 | 
					</video>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<%= htmlWebpackPlugin.tags.bodyTags %>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    var instance = fluidPlayer('fluid-player-e2e-case');
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										44
									
								
								client/fluid-player/test/html/dash_live_vast.tpl.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,44 @@
 | 
				
			||||||
 | 
					<!doctype html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
 | 
					    <meta name="viewport"
 | 
				
			||||||
 | 
					          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
 | 
				
			||||||
 | 
					    <meta http-equiv="X-UA-Compatible" content="ie=edge">
 | 
				
			||||||
 | 
					    <title>DASH Live Stream with VAST</title>
 | 
				
			||||||
 | 
					    <%= htmlWebpackPlugin.tags.headTags %>
 | 
				
			||||||
 | 
					    <style lang="css">
 | 
				
			||||||
 | 
					        #fluid-player-e2e-case {
 | 
				
			||||||
 | 
					            width: 90%;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<video id="fluid-player-e2e-case">
 | 
				
			||||||
 | 
					    <source src="https://dash.akamaized.net/dash264/TestCasesIOP33/adapatationSetSwitching/5/manifest.mpd" type="application/dash+xml"/>
 | 
				
			||||||
 | 
					</video>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<%= htmlWebpackPlugin.tags.bodyTags %>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    var instance = fluidPlayer('fluid-player-e2e-case',{
 | 
				
			||||||
 | 
					        vastOptions: {
 | 
				
			||||||
 | 
					            allowVPAID: true, // Default false.
 | 
				
			||||||
 | 
					            adList: [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    roll: 'preRoll',
 | 
				
			||||||
 | 
					                    vastTag: '/static/vast_linear.xml'
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    roll: 'midRoll',
 | 
				
			||||||
 | 
					                    vastTag: '/static/vast_nonlinear.xml',
 | 
				
			||||||
 | 
					                    timer: 4
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										29
									
								
								client/fluid-player/test/html/dash_vod.tpl.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,29 @@
 | 
				
			||||||
 | 
					<!doctype html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
 | 
					    <meta name="viewport"
 | 
				
			||||||
 | 
					          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
 | 
				
			||||||
 | 
					    <meta http-equiv="X-UA-Compatible" content="ie=edge">
 | 
				
			||||||
 | 
					    <title>DASH VOD</title>
 | 
				
			||||||
 | 
					    <%= htmlWebpackPlugin.tags.headTags %>
 | 
				
			||||||
 | 
					    <style lang="css">
 | 
				
			||||||
 | 
					        #fluid-player-e2e-case {
 | 
				
			||||||
 | 
					            width: 90%;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<video id="fluid-player-e2e-case">
 | 
				
			||||||
 | 
					    <source src="https://dash.akamaized.net/dash264/TestCases/1a/sony/SNE_DASH_SD_CASE1A_REVISED.mpd" type="application/dash+xml"/>
 | 
				
			||||||
 | 
					</video>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<%= htmlWebpackPlugin.tags.bodyTags %>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    var instance = fluidPlayer('fluid-player-e2e-case');
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										44
									
								
								client/fluid-player/test/html/dash_vod_vast.tpl.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,44 @@
 | 
				
			||||||
 | 
					<!doctype html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
 | 
					    <meta name="viewport"
 | 
				
			||||||
 | 
					          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
 | 
				
			||||||
 | 
					    <meta http-equiv="X-UA-Compatible" content="ie=edge">
 | 
				
			||||||
 | 
					    <title>DASH VOD with VAST</title>
 | 
				
			||||||
 | 
					    <%= htmlWebpackPlugin.tags.headTags %>
 | 
				
			||||||
 | 
					    <style lang="css">
 | 
				
			||||||
 | 
					        #fluid-player-e2e-case {
 | 
				
			||||||
 | 
					            width: 90%;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<video id="fluid-player-e2e-case">
 | 
				
			||||||
 | 
					    <source src="https://dash.akamaized.net/dash264/TestCases/1a/sony/SNE_DASH_SD_CASE1A_REVISED.mpd" type="application/dash+xml"/>
 | 
				
			||||||
 | 
					</video>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<%= htmlWebpackPlugin.tags.bodyTags %>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    var instance = fluidPlayer('fluid-player-e2e-case', {
 | 
				
			||||||
 | 
					        vastOptions: {
 | 
				
			||||||
 | 
					            allowVPAID: true, // Default false.
 | 
				
			||||||
 | 
					            adList: [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    roll: 'preRoll',
 | 
				
			||||||
 | 
					                    vastTag: '/static/vast_linear.xml'
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    roll: 'midRoll',
 | 
				
			||||||
 | 
					                    vastTag: '/static/vast_nonlinear.xml',
 | 
				
			||||||
 | 
					                    timer: 4
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										48
									
								
								client/fluid-player/test/html/e2e/ads_linear.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,48 @@
 | 
				
			||||||
 | 
					<!doctype html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
 | 
					    <meta content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
 | 
				
			||||||
 | 
					          name="viewport">
 | 
				
			||||||
 | 
					    <meta content="ie=edge" http-equiv="X-UA-Compatible">
 | 
				
			||||||
 | 
					    <title>E2E with VAST Linear</title>
 | 
				
			||||||
 | 
					    <%= htmlWebpackPlugin.tags.headTags %>
 | 
				
			||||||
 | 
					    <style lang="css">
 | 
				
			||||||
 | 
					        #fluid-player-e2e-case {
 | 
				
			||||||
 | 
					            width: 90%;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<video id="fluid-player-e2e-case">
 | 
				
			||||||
 | 
					    <source src="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4" data-fluid-hd title="1080p" type="video/mp4"/>
 | 
				
			||||||
 | 
					</video>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<%= htmlWebpackPlugin.tags.bodyTags %>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    var instance = fluidPlayer('fluid-player-e2e-case', {
 | 
				
			||||||
 | 
					        vastOptions: {
 | 
				
			||||||
 | 
					            allowVPAID: true, // Default false.
 | 
				
			||||||
 | 
					            adList: [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    roll: 'preRoll',
 | 
				
			||||||
 | 
					                    vastTag: '/static/vast_linear_e2e.xml',
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    roll: 'midRoll',
 | 
				
			||||||
 | 
					                    vastTag: '/static/vast_linear_e2e.xml',
 | 
				
			||||||
 | 
					                    timer: 40,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    roll: 'postRoll',
 | 
				
			||||||
 | 
					                    vastTag: '/static/vast_linear_e2e.xml',
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										37
									
								
								client/fluid-player/test/html/e2e/controls.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,37 @@
 | 
				
			||||||
 | 
					<!doctype html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
 | 
					    <meta name="viewport"
 | 
				
			||||||
 | 
					          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
 | 
				
			||||||
 | 
					    <meta http-equiv="X-UA-Compatible" content="ie=edge">
 | 
				
			||||||
 | 
					    <title>Controls</title>
 | 
				
			||||||
 | 
					    <%= htmlWebpackPlugin.tags.headTags %>
 | 
				
			||||||
 | 
					    <style lang="css">
 | 
				
			||||||
 | 
					        #fluid-player-e2e-case {
 | 
				
			||||||
 | 
					            width: 90%;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<video id="fluid-player-e2e-case">
 | 
				
			||||||
 | 
					    <source src="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerMeltdowns.mp4" data-fluid-hd title="1080p" type="video/mp4"/>
 | 
				
			||||||
 | 
					</video>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<%= htmlWebpackPlugin.tags.bodyTags %>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    var instance = fluidPlayer('fluid-player-e2e-case', {
 | 
				
			||||||
 | 
					        layoutControls: {
 | 
				
			||||||
 | 
					            controlBar: {
 | 
				
			||||||
 | 
					                autoHide: true,
 | 
				
			||||||
 | 
					                autoHideTimeout: 1,
 | 
				
			||||||
 | 
					                animated: true
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,42 @@
 | 
				
			||||||
 | 
					<!doctype html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
 | 
					    <meta name="viewport"
 | 
				
			||||||
 | 
					          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
 | 
				
			||||||
 | 
					    <meta http-equiv="X-UA-Compatible" content="ie=edge">
 | 
				
			||||||
 | 
					    <title>Suggested videos with subtitles</title>
 | 
				
			||||||
 | 
					    <%= htmlWebpackPlugin.tags.headTags %>
 | 
				
			||||||
 | 
					    <style lang="css">
 | 
				
			||||||
 | 
					        #fluid-player-e2e-case, #fluid-player-e2e-case-2 {
 | 
				
			||||||
 | 
					            width: 90%;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<video id="fluid-player-e2e-case">
 | 
				
			||||||
 | 
					    <source src="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4" type="video/mp4"/>
 | 
				
			||||||
 | 
					</video>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<%= htmlWebpackPlugin.tags.bodyTags %>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    var instance = fluidPlayer('fluid-player-e2e-case', {
 | 
				
			||||||
 | 
					        suggestedVideos: {
 | 
				
			||||||
 | 
					            configUrl: '/static/suggested_videos_example_v1.json',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        vastOptions: {
 | 
				
			||||||
 | 
					            allowVPAID: true, // Default false.
 | 
				
			||||||
 | 
					            adList: [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    roll: 'postRoll',
 | 
				
			||||||
 | 
					                    vastTag: '/static/vast_linear_e2e.xml',
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										29
									
								
								client/fluid-player/test/html/hls_live.tpl.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,29 @@
 | 
				
			||||||
 | 
					<!doctype html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
 | 
					    <meta name="viewport"
 | 
				
			||||||
 | 
					          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
 | 
				
			||||||
 | 
					    <meta http-equiv="X-UA-Compatible" content="ie=edge">
 | 
				
			||||||
 | 
					    <title>HLS Live Stream</title>
 | 
				
			||||||
 | 
					    <%= htmlWebpackPlugin.tags.headTags %>
 | 
				
			||||||
 | 
					    <style lang="css">
 | 
				
			||||||
 | 
					        #fluid-player-e2e-case {
 | 
				
			||||||
 | 
					            width: 90%;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<video id="fluid-player-e2e-case">
 | 
				
			||||||
 | 
					    <source src="https://cph-msl.akamaized.net/hls/live/2000341/test/master.m3u8" type="application/x-mpegURL"/>
 | 
				
			||||||
 | 
					</video>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<%= htmlWebpackPlugin.tags.bodyTags %>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    var instance = fluidPlayer('fluid-player-e2e-case');
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										44
									
								
								client/fluid-player/test/html/hls_live_vast.tpl.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,44 @@
 | 
				
			||||||
 | 
					<!doctype html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
 | 
					    <meta name="viewport"
 | 
				
			||||||
 | 
					          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
 | 
				
			||||||
 | 
					    <meta http-equiv="X-UA-Compatible" content="ie=edge">
 | 
				
			||||||
 | 
					    <title>HLS Live Stream with VAST</title>
 | 
				
			||||||
 | 
					    <%= htmlWebpackPlugin.tags.headTags %>
 | 
				
			||||||
 | 
					    <style lang="css">
 | 
				
			||||||
 | 
					        #fluid-player-e2e-case {
 | 
				
			||||||
 | 
					            width: 90%;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<video id="fluid-player-e2e-case">
 | 
				
			||||||
 | 
					    <source src="https://cph-msl.akamaized.net/hls/live/2000341/test/master.m3u8" type="application/x-mpegURL"/>
 | 
				
			||||||
 | 
					</video>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<%= htmlWebpackPlugin.tags.bodyTags %>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    var instance = fluidPlayer('fluid-player-e2e-case', {
 | 
				
			||||||
 | 
					        vastOptions: {
 | 
				
			||||||
 | 
					            allowVPAID: true, // Default false.
 | 
				
			||||||
 | 
					            adList: [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    roll: 'preRoll',
 | 
				
			||||||
 | 
					                    vastTag: '/static/vast_linear.xml'
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    roll: 'midRoll',
 | 
				
			||||||
 | 
					                    vastTag: '/static/vast_nonlinear.xml',
 | 
				
			||||||
 | 
					                    timer: 4
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										29
									
								
								client/fluid-player/test/html/hls_vod.tpl.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,29 @@
 | 
				
			||||||
 | 
					<!doctype html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
 | 
					    <meta name="viewport"
 | 
				
			||||||
 | 
					          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
 | 
				
			||||||
 | 
					    <meta http-equiv="X-UA-Compatible" content="ie=edge">
 | 
				
			||||||
 | 
					    <title>HLS VOD</title>
 | 
				
			||||||
 | 
					    <%= htmlWebpackPlugin.tags.headTags %>
 | 
				
			||||||
 | 
					    <style lang="css">
 | 
				
			||||||
 | 
					        #fluid-player-e2e-case {
 | 
				
			||||||
 | 
					            width: 90%;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<video id="fluid-player-e2e-case">
 | 
				
			||||||
 | 
					    <source src="https://demo.unified-streaming.com/k8s/features/stable/video/tears-of-steel/tears-of-steel.ism/.m3u8" type="application/x-mpegURL"/>
 | 
				
			||||||
 | 
					</video>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<%= htmlWebpackPlugin.tags.bodyTags %>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    var instance = fluidPlayer('fluid-player-e2e-case');
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										51
									
								
								client/fluid-player/test/html/hls_vod_suggested_videos.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,51 @@
 | 
				
			||||||
 | 
					<!doctype html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
 | 
					    <meta name="viewport"
 | 
				
			||||||
 | 
					          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
 | 
				
			||||||
 | 
					    <meta http-equiv="X-UA-Compatible" content="ie=edge">
 | 
				
			||||||
 | 
					    <title>Hls VOD suggested videos</title>
 | 
				
			||||||
 | 
					    <%= htmlWebpackPlugin.tags.headTags %>
 | 
				
			||||||
 | 
					    <style lang="css">
 | 
				
			||||||
 | 
					        #fluid-player-e2e-case, #fluid-player-e2e-case-2 {
 | 
				
			||||||
 | 
					            width: 90%;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<h1>Hls with suggested videos</h1>
 | 
				
			||||||
 | 
					<aside><b>NOTE</b>: Only Hls supports auto quality, for now</aside>
 | 
				
			||||||
 | 
					<ul>
 | 
				
			||||||
 | 
					    <li>Initial video is an mp4</li>
 | 
				
			||||||
 | 
					    <ul>
 | 
				
			||||||
 | 
					        <li>no auto option</li>
 | 
				
			||||||
 | 
					    </ul>
 | 
				
			||||||
 | 
					    <li>All suggested videos in the initial video are Hls</li>
 | 
				
			||||||
 | 
					    <li>Suggested videos at the end of the second video are mp4's</li>
 | 
				
			||||||
 | 
					    <ul>
 | 
				
			||||||
 | 
					        <li>no auto option</li>
 | 
				
			||||||
 | 
					    </ul>
 | 
				
			||||||
 | 
					</ul>
 | 
				
			||||||
 | 
					<video id="fluid-player-e2e-case">
 | 
				
			||||||
 | 
					    <source src='https://www.fluidplayer.com/videos/In-video-Banner.mp4' data-fluid-hd title="1080p" type='video/mp4'/>
 | 
				
			||||||
 | 
					    <source src='https://www.fluidplayer.com/videos/In-video-Banner.mp4' data-fluid-hd title="720p" type='video/mp4'/>
 | 
				
			||||||
 | 
					    <source src='https://www.fluidplayer.com/videos/In-video-Banner.mp4' title="480p" type='video/mp4'/>
 | 
				
			||||||
 | 
					</video>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<%= htmlWebpackPlugin.tags.bodyTags %>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    var instance = fluidPlayer('fluid-player-e2e-case', {
 | 
				
			||||||
 | 
					        layoutControls: {
 | 
				
			||||||
 | 
					            subtitlesEnabled: true,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        suggestedVideos: {
 | 
				
			||||||
 | 
					            configUrl: '/static/suggested_videos_example_v3.json',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										44
									
								
								client/fluid-player/test/html/hls_vod_vast.tpl.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,44 @@
 | 
				
			||||||
 | 
					<!doctype html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
 | 
					    <meta name="viewport"
 | 
				
			||||||
 | 
					          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
 | 
				
			||||||
 | 
					    <meta http-equiv="X-UA-Compatible" content="ie=edge">
 | 
				
			||||||
 | 
					    <title>HLS VOD with VAST</title>
 | 
				
			||||||
 | 
					    <%= htmlWebpackPlugin.tags.headTags %>
 | 
				
			||||||
 | 
					    <style lang="css">
 | 
				
			||||||
 | 
					        #fluid-player-e2e-case {
 | 
				
			||||||
 | 
					            width: 90%;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<video id="fluid-player-e2e-case">
 | 
				
			||||||
 | 
					    <source src="https://demo.unified-streaming.com/k8s/features/stable/video/tears-of-steel/tears-of-steel.ism/.m3u8" type="application/x-mpegURL"/>
 | 
				
			||||||
 | 
					</video>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<%= htmlWebpackPlugin.tags.bodyTags %>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    var instance = fluidPlayer('fluid-player-e2e-case', {
 | 
				
			||||||
 | 
					        vastOptions: {
 | 
				
			||||||
 | 
					            allowVPAID: true, // Default false.
 | 
				
			||||||
 | 
					            adList: [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    roll: 'preRoll',
 | 
				
			||||||
 | 
					                    vastTag: '/static/vast_linear.xml'
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    roll: 'midRoll',
 | 
				
			||||||
 | 
					                    vastTag: '/static/vast_nonlinear.xml',
 | 
				
			||||||
 | 
					                    timer: 4
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,82 @@
 | 
				
			||||||
 | 
					<!doctype html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
 | 
					    <meta name="viewport"
 | 
				
			||||||
 | 
					          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
 | 
				
			||||||
 | 
					    <meta http-equiv="X-UA-Compatible" content="ie=edge">
 | 
				
			||||||
 | 
					    <title>Player Reinitialization</title>
 | 
				
			||||||
 | 
					    <%= htmlWebpackPlugin.tags.headTags %>
 | 
				
			||||||
 | 
					    <style lang="css">
 | 
				
			||||||
 | 
					        #fluid-player-e2e-case {
 | 
				
			||||||
 | 
					            width: 90%;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div id="target"></div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<br/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<button id="destroy" role="button">Destroy</button>
 | 
				
			||||||
 | 
					<button id="newInstance" role="button">Create New Instance</button>
 | 
				
			||||||
 | 
					<button id="cd" role="button">Create and Destroy</button>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<hr/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<p>This test is meant to check if the Fluid Player instance is properly clean up after destroying it</p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<%= htmlWebpackPlugin.tags.bodyTags %>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    let instance;
 | 
				
			||||||
 | 
					    const targetElm = document.getElementById('target')
 | 
				
			||||||
 | 
					    const destroyBtn = document.getElementById('destroy');
 | 
				
			||||||
 | 
					    const newInstanceBtn = document.getElementById('newInstance');
 | 
				
			||||||
 | 
					    const cdButton = document.getElementById('cd');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function destroy() {
 | 
				
			||||||
 | 
					        instance.destroy();
 | 
				
			||||||
 | 
					        instance = undefined;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function create() {
 | 
				
			||||||
 | 
					        if (instance) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        targetElm.innerHTML = `
 | 
				
			||||||
 | 
					            <video id="fluid-player-e2e-case">
 | 
				
			||||||
 | 
					                <source src="https://cdn.fluidplayer.com/videos/valerian-1080p.mkv" data-fluid-hd title="1080p" type="video/mp4"/>
 | 
				
			||||||
 | 
					                <source src="https://cdn.fluidplayer.com/videos/valerian-720p.mkv" data-fluid-hd title="720p" type="video/mp4"/>
 | 
				
			||||||
 | 
					                <source src="https://cdn.fluidplayer.com/videos/valerian-480p.mkv" title="480p" type="video/mp4"/>
 | 
				
			||||||
 | 
					            </video>
 | 
				
			||||||
 | 
					        `;
 | 
				
			||||||
 | 
					        instance = fluidPlayer('fluid-player-e2e-case', {
 | 
				
			||||||
 | 
					            layoutControls: {
 | 
				
			||||||
 | 
					                allowDownload: true
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            vastOptions: {
 | 
				
			||||||
 | 
					                adList: [
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        vastTag: '/static/vast_nonlinear.xml',
 | 
				
			||||||
 | 
					                        roll: 'preRoll',
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        vastTag: '/static/vast_nonlinear.xml',
 | 
				
			||||||
 | 
					                        roll: 'midRoll',
 | 
				
			||||||
 | 
					                        timer: 10,
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    destroyBtn.onclick = destroy;
 | 
				
			||||||
 | 
					    newInstanceBtn.onclick = create;
 | 
				
			||||||
 | 
					    cdButton.onclick = () => { create(); destroy(); }
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										42
									
								
								client/fluid-player/test/html/skip_return.tpl.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,42 @@
 | 
				
			||||||
 | 
					<!doctype html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
 | 
					    <meta name="viewport"
 | 
				
			||||||
 | 
					          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
 | 
				
			||||||
 | 
					    <meta http-equiv="X-UA-Compatible" content="ie=edge">
 | 
				
			||||||
 | 
					    <title>Skip & Return</title>
 | 
				
			||||||
 | 
					    <%= htmlWebpackPlugin.tags.headTags %>
 | 
				
			||||||
 | 
					    <style lang="css">
 | 
				
			||||||
 | 
					        #fluid-player-e2e-case {
 | 
				
			||||||
 | 
					            width: 90%;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<video id="fluid-player-e2e-case">
 | 
				
			||||||
 | 
					    <source src="https://cdn.fluidplayer.com/videos/valerian-1080p.mkv" data-fluid-hd title="1080p" type="video/mp4"/>
 | 
				
			||||||
 | 
					    <source src="https://cdn.fluidplayer.com/videos/valerian-720p.mkv" data-fluid-hd title="720p" type="video/mp4"/>
 | 
				
			||||||
 | 
					    <source src="https://cdn.fluidplayer.com/videos/valerian-480p.mkv" title="480p" type="video/mp4"/>
 | 
				
			||||||
 | 
					</video>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<%= htmlWebpackPlugin.tags.bodyTags %>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    var instance = fluidPlayer('fluid-player-e2e-case', {
 | 
				
			||||||
 | 
					        layoutControls: {
 | 
				
			||||||
 | 
					            controlForwardBackward: {
 | 
				
			||||||
 | 
					                show: true
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            controlBar: {
 | 
				
			||||||
 | 
					                autoHide: true, // Default false
 | 
				
			||||||
 | 
					                autoHideTimeout: 3, // Default 3
 | 
				
			||||||
 | 
					                animated: false // Default true
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										18
									
								
								client/fluid-player/test/html/special-cases/_README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,18 @@
 | 
				
			||||||
 | 
					# Special cases
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Templates put here will not appear in the e2e file list, but will be accessible directly by URL in the root of the test server.
 | 
				
			||||||
 | 
					These templates should be used to share a link of an issue to an issue when creating a PR.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					To avoid conflicts with any naming, always use identifiers that point to the cause, for example:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* Internal issues: `internal-vast-click-tracking-issue.tpl.html`
 | 
				
			||||||
 | 
					* Github issues: `issue-215.tpl.html`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					These files would be accessible in the following URLs when running the dev server:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* http://localhost:8080/internal-vast-click-tracking-issue.html
 | 
				
			||||||
 | 
					* http://localhost:8080/issues-215.html
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					To use specific static files, please create them in `test/static/special-cases`, no special naming is required.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					For more information about how these files are being loaded, check `webpack.config.js`
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,38 @@
 | 
				
			||||||
 | 
					<!doctype html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
 | 
					    <meta name="viewport"
 | 
				
			||||||
 | 
					          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
 | 
				
			||||||
 | 
					    <meta http-equiv="X-UA-Compatible" content="ie=edge">
 | 
				
			||||||
 | 
					    <title>Fluid player doesn't track clicks on some sites</title>
 | 
				
			||||||
 | 
					    <%= htmlWebpackPlugin.tags.headTags %>
 | 
				
			||||||
 | 
					    <style lang="css">
 | 
				
			||||||
 | 
					        #fluid-player-e2e-case {
 | 
				
			||||||
 | 
					            width: 90%;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<video id="fluid-player-e2e-case">
 | 
				
			||||||
 | 
					    <source src="https://cdn.fluidplayer.com/videos/valerian-1080p.mkv" data-fluid-hd title="1080p" type="video/mp4"/>
 | 
				
			||||||
 | 
					    <source src="https://cdn.fluidplayer.com/videos/valerian-720p.mkv" data-fluid-hd title="720p" type="video/mp4"/>
 | 
				
			||||||
 | 
					    <source src="https://cdn.fluidplayer.com/videos/valerian-480p.mkv" title="480p" type="video/mp4"/>
 | 
				
			||||||
 | 
					</video>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<%= htmlWebpackPlugin.tags.bodyTags %>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    var instance = fluidPlayer('fluid-player-e2e-case', {
 | 
				
			||||||
 | 
					        vastOptions: {
 | 
				
			||||||
 | 
					            adList: [{
 | 
				
			||||||
 | 
					                vastTag: 'static/special-cases/fp-215.xml',
 | 
				
			||||||
 | 
					                roll: 'preRoll'
 | 
				
			||||||
 | 
					            }]
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,34 @@
 | 
				
			||||||
 | 
					<!doctype html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
 | 
					    <meta name="viewport"
 | 
				
			||||||
 | 
					          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
 | 
				
			||||||
 | 
					    <meta http-equiv="X-UA-Compatible" content="ie=edge">
 | 
				
			||||||
 | 
					    <title>Issue 702 - CurrentTime reset after switch HLS source on IOS</title>
 | 
				
			||||||
 | 
					    <%= htmlWebpackPlugin.tags.headTags %>
 | 
				
			||||||
 | 
					    <style lang="css">
 | 
				
			||||||
 | 
					        #fluid-player-e2e-case {
 | 
				
			||||||
 | 
					            width: 90%;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<video id="fluid-player-e2e-case">
 | 
				
			||||||
 | 
					    <source src="https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8?a=1" type="application/x-mpegURL" title="1">
 | 
				
			||||||
 | 
					    <source src="https://demo.unified-streaming.com/k8s/features/stable/video/tears-of-steel/tears-of-steel.mp4/.m3u8" type="application/x-mpegURL" title="2">
 | 
				
			||||||
 | 
					</video>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<%= htmlWebpackPlugin.tags.bodyTags %>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    var instance = fluidPlayer('fluid-player-e2e-case', {
 | 
				
			||||||
 | 
					        vastOptions: {
 | 
				
			||||||
 | 
					            allowVPAID: true, // Default false.
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										66
									
								
								client/fluid-player/test/html/suggested_videos_ads.tpl.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,66 @@
 | 
				
			||||||
 | 
					<!doctype html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
 | 
					    <meta name="viewport"
 | 
				
			||||||
 | 
					          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
 | 
				
			||||||
 | 
					    <meta http-equiv="X-UA-Compatible" content="ie=edge">
 | 
				
			||||||
 | 
					    <title>Suggested videos with ads</title>
 | 
				
			||||||
 | 
					    <%= htmlWebpackPlugin.tags.headTags %>
 | 
				
			||||||
 | 
					    <style lang="css">
 | 
				
			||||||
 | 
					        #fluid-player-e2e-case {
 | 
				
			||||||
 | 
					            width: 90%;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<video id="fluid-player-e2e-case">
 | 
				
			||||||
 | 
					    <source src='https://cdn.fluidplayer.com/videos/valerian-1080p.mkv' data-fluid-hd title="1080p" type='video/mp4'/>
 | 
				
			||||||
 | 
					    <source src='https://cdn.fluidplayer.com/videos/valerian-720p.mkv' data-fluid-hd title="720p" type='video/mp4'/>
 | 
				
			||||||
 | 
					    <source src='http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4' title="480p" type='video/mp4'/>
 | 
				
			||||||
 | 
					</video>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<%= htmlWebpackPlugin.tags.bodyTags %>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    var instance = fluidPlayer('fluid-player-e2e-case', {
 | 
				
			||||||
 | 
					        layoutControls: {
 | 
				
			||||||
 | 
					            subtitlesEnabled: true,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        suggestedVideos: {
 | 
				
			||||||
 | 
					            configUrl: '/static/suggested_videos_example_v1.json',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        vastOptions: {
 | 
				
			||||||
 | 
					            allowVPAID: true, // Default false.
 | 
				
			||||||
 | 
					            adList: [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    roll: 'preRoll',
 | 
				
			||||||
 | 
					                    vastTag: '/static/vast_linear.xml',
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    roll: 'midRoll',
 | 
				
			||||||
 | 
					                    vastTag: '/static/vast_nonlinear.xml',
 | 
				
			||||||
 | 
					                    timer: '2%',
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    roll: 'midRoll',
 | 
				
			||||||
 | 
					                    vastTag: '/static/vast_linear.xml',
 | 
				
			||||||
 | 
					                    timer: '5%',
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    roll: 'midRoll',
 | 
				
			||||||
 | 
					                    vastTag: '/static/vast_linear.xml',
 | 
				
			||||||
 | 
					                    timer: 50,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    roll: 'postRoll',
 | 
				
			||||||
 | 
					                    vastTag: '/static/vast_linear.xml',
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,57 @@
 | 
				
			||||||
 | 
					<!doctype html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
 | 
					    <meta name="viewport"
 | 
				
			||||||
 | 
					          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
 | 
				
			||||||
 | 
					    <meta http-equiv="X-UA-Compatible" content="ie=edge">
 | 
				
			||||||
 | 
					    <title>Suggested videos with subtitles</title>
 | 
				
			||||||
 | 
					    <%= htmlWebpackPlugin.tags.headTags %>
 | 
				
			||||||
 | 
					    <style lang="css">
 | 
				
			||||||
 | 
					        #fluid-player-e2e-case, #fluid-player-e2e-case-2 {
 | 
				
			||||||
 | 
					            width: 90%;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<h1>Video with no subtitles initially, two video sources</h1>
 | 
				
			||||||
 | 
					<video id="fluid-player-e2e-case">
 | 
				
			||||||
 | 
					    <source src='https://cdn.fluidplayer.com/videos/valerian-1080p.mkv' data-fluid-hd title="1080p" type='video/mp4'/>
 | 
				
			||||||
 | 
					    <source src='https://cdn.fluidplayer.com/videos/valerian-720p.mkv' data-fluid-hd title="720p" type='video/mp4'/>
 | 
				
			||||||
 | 
					    <source src='http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4' title="480p" type='video/mp4'/>
 | 
				
			||||||
 | 
					</video>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<h1>Video with no subtitles initially, only 1 video source</h1>
 | 
				
			||||||
 | 
					<video id="fluid-player-e2e-case-2">
 | 
				
			||||||
 | 
					    <source src="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4" type="video/mp4"/>
 | 
				
			||||||
 | 
					    <track label="English" kind="metadata" srclang="en" src="/static/subtitles/english.vtt" default>
 | 
				
			||||||
 | 
					    <track label="Deutsch" kind="metadata" srclang="de" src="/static/subtitles/deutsch.vtt">
 | 
				
			||||||
 | 
					</video>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<%= htmlWebpackPlugin.tags.bodyTags %>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    var instance = fluidPlayer('fluid-player-e2e-case', {
 | 
				
			||||||
 | 
					        layoutControls: {
 | 
				
			||||||
 | 
					            subtitlesEnabled: true,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        suggestedVideos: {
 | 
				
			||||||
 | 
					            configUrl: '/static/suggested_videos_example_v1.json',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    var instance = fluidPlayer('fluid-player-e2e-case-2', {
 | 
				
			||||||
 | 
					        layoutControls: {
 | 
				
			||||||
 | 
					            subtitlesEnabled: true,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        suggestedVideos: {
 | 
				
			||||||
 | 
					            configUrl: '/static/suggested_videos_example_v1.json',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										31
									
								
								client/fluid-player/test/html/vod_basic.tpl.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,31 @@
 | 
				
			||||||
 | 
					<!doctype html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
 | 
					    <meta name="viewport"
 | 
				
			||||||
 | 
					          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
 | 
				
			||||||
 | 
					    <meta http-equiv="X-UA-Compatible" content="ie=edge">
 | 
				
			||||||
 | 
					    <title>VOD</title>
 | 
				
			||||||
 | 
					    <%= htmlWebpackPlugin.tags.headTags %>
 | 
				
			||||||
 | 
					    <style lang="css">
 | 
				
			||||||
 | 
					        #fluid-player-e2e-case {
 | 
				
			||||||
 | 
					            width: 90%;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<video id="fluid-player-e2e-case">
 | 
				
			||||||
 | 
					    <source src="https://cdn.fluidplayer.com/videos/valerian-1080p.mkv" data-fluid-hd title="1080p" type="video/mp4"/>
 | 
				
			||||||
 | 
					    <source src="https://cdn.fluidplayer.com/videos/valerian-720p.mkv" data-fluid-hd title="720p" type="video/mp4"/>
 | 
				
			||||||
 | 
					    <source src="https://cdn.fluidplayer.com/videos/valerian-480p.mkv" title="480p" type="video/mp4"/>
 | 
				
			||||||
 | 
					</video>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<%= htmlWebpackPlugin.tags.bodyTags %>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    var instance = fluidPlayer('fluid-player-e2e-case');
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										39
									
								
								client/fluid-player/test/html/vod_basic_autohide.tpl.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,39 @@
 | 
				
			||||||
 | 
					<!doctype html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
 | 
					    <meta name="viewport"
 | 
				
			||||||
 | 
					          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
 | 
				
			||||||
 | 
					    <meta http-equiv="X-UA-Compatible" content="ie=edge">
 | 
				
			||||||
 | 
					    <title>VOD auto-hide toolbar</title>
 | 
				
			||||||
 | 
					    <%= htmlWebpackPlugin.tags.headTags %>
 | 
				
			||||||
 | 
					    <style lang="css">
 | 
				
			||||||
 | 
					        #fluid-player-e2e-case {
 | 
				
			||||||
 | 
					            width: 90%;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<video id="fluid-player-e2e-case">
 | 
				
			||||||
 | 
					    <source src="https://cdn.fluidplayer.com/videos/valerian-1080p.mkv" data-fluid-hd title="1080p" type="video/mp4"/>
 | 
				
			||||||
 | 
					    <source src="https://cdn.fluidplayer.com/videos/valerian-720p.mkv" data-fluid-hd title="720p" type="video/mp4"/>
 | 
				
			||||||
 | 
					    <source src="https://cdn.fluidplayer.com/videos/valerian-480p.mkv" title="480p" type="video/mp4"/>
 | 
				
			||||||
 | 
					</video>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<%= htmlWebpackPlugin.tags.bodyTags %>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    var instance = fluidPlayer('fluid-player-e2e-case', {
 | 
				
			||||||
 | 
					        layoutControls: {
 | 
				
			||||||
 | 
					            controlBar: {
 | 
				
			||||||
 | 
					                autoHide: true,
 | 
				
			||||||
 | 
					                autoHideTimeout: 1,
 | 
				
			||||||
 | 
					                animated: true
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										32
									
								
								client/fluid-player/test/html/vod_basic_by_ref.tpl.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,32 @@
 | 
				
			||||||
 | 
					<!doctype html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
 | 
					    <meta name="viewport"
 | 
				
			||||||
 | 
					          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
 | 
				
			||||||
 | 
					    <meta http-equiv="X-UA-Compatible" content="ie=edge">
 | 
				
			||||||
 | 
					    <title>VOD using reference</title>
 | 
				
			||||||
 | 
					    <%= htmlWebpackPlugin.tags.headTags %>
 | 
				
			||||||
 | 
					    <style lang="css">
 | 
				
			||||||
 | 
					        video[data-ref="player-by-ref"] {
 | 
				
			||||||
 | 
					            width: 90%;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<video data-ref="player-by-ref">
 | 
				
			||||||
 | 
					    <source src="https://cdn.fluidplayer.com/videos/valerian-1080p.mkv" data-fluid-hd title="1080p" type="video/mp4"/>
 | 
				
			||||||
 | 
					    <source src="https://cdn.fluidplayer.com/videos/valerian-720p.mkv" data-fluid-hd title="720p" type="video/mp4"/>
 | 
				
			||||||
 | 
					    <source src="https://cdn.fluidplayer.com/videos/valerian-480p.mkv" title="480p" type="video/mp4"/>
 | 
				
			||||||
 | 
					</video>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<%= htmlWebpackPlugin.tags.bodyTags %>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    const target = document.querySelector('video[data-ref="player-by-ref"]');
 | 
				
			||||||
 | 
					    const instance = fluidPlayer(target);
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,42 @@
 | 
				
			||||||
 | 
					<!doctype html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
 | 
					    <meta name="viewport"
 | 
				
			||||||
 | 
					          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
 | 
				
			||||||
 | 
					    <meta http-equiv="X-UA-Compatible" content="ie=edge">
 | 
				
			||||||
 | 
					    <title>VOD with CTA set from Player Configurations</title>
 | 
				
			||||||
 | 
					    <%= htmlWebpackPlugin.tags.headTags %>
 | 
				
			||||||
 | 
					    <style lang="css">
 | 
				
			||||||
 | 
					        #fluid-player-e2e-case {
 | 
				
			||||||
 | 
					            width: 90%;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<video id="fluid-player-e2e-case">
 | 
				
			||||||
 | 
					    <source src="https://cdn.fluidplayer.com/videos/valerian-1080p.mkv" data-fluid-hd title="1080p" type="video/mp4"/>
 | 
				
			||||||
 | 
					    <source src="https://cdn.fluidplayer.com/videos/valerian-720p.mkv" data-fluid-hd title="720p" type="video/mp4"/>
 | 
				
			||||||
 | 
					    <source src="https://cdn.fluidplayer.com/videos/valerian-480p.mkv" title="480p" type="video/mp4"/>
 | 
				
			||||||
 | 
					</video>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<%= htmlWebpackPlugin.tags.bodyTags %>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    var instance = fluidPlayer('fluid-player-e2e-case', {
 | 
				
			||||||
 | 
					        vastOptions: {
 | 
				
			||||||
 | 
					            adCTAText: 'Visit Now! *Custom CTA*', // Note: Will only show if VAST tag doesn't have TitleCTA
 | 
				
			||||||
 | 
					            adCTATextPosition: 'bottom right',
 | 
				
			||||||
 | 
					            adList: [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    roll: 'preRoll',
 | 
				
			||||||
 | 
					                    vastTag: '/static/vast_cta.xml', // VAST Tag with TitleCTA extension with CTA information
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,50 @@
 | 
				
			||||||
 | 
					<!doctype html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
 | 
					    <meta name="viewport"
 | 
				
			||||||
 | 
					          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
 | 
				
			||||||
 | 
					    <meta http-equiv="X-UA-Compatible" content="ie=edge">
 | 
				
			||||||
 | 
					    <title>VOD with CTA set from VAST</title>
 | 
				
			||||||
 | 
					    <%= htmlWebpackPlugin.tags.headTags %>
 | 
				
			||||||
 | 
					    <style lang="css">
 | 
				
			||||||
 | 
					        #fluid-player-e2e-case {
 | 
				
			||||||
 | 
					            width: 100%;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<video id="fluid-player-e2e-case">
 | 
				
			||||||
 | 
					    <source src="https://cdn.fluidplayer.com/videos/valerian-1080p.mkv" data-fluid-hd title="1080p" type="video/mp4"/>
 | 
				
			||||||
 | 
					    <source src="https://cdn.fluidplayer.com/videos/valerian-720p.mkv" data-fluid-hd title="720p" type="video/mp4"/>
 | 
				
			||||||
 | 
					    <source src="https://cdn.fluidplayer.com/videos/valerian-480p.mkv" title="480p" type="video/mp4"/>
 | 
				
			||||||
 | 
					</video>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<%= htmlWebpackPlugin.tags.bodyTags %>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    var instance = fluidPlayer('fluid-player-e2e-case', {
 | 
				
			||||||
 | 
					        layoutControls: {
 | 
				
			||||||
 | 
					            controlBar: {
 | 
				
			||||||
 | 
					                autoHide:           true,
 | 
				
			||||||
 | 
					                autoHideTimeout:    1,
 | 
				
			||||||
 | 
					                animated:           true
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        vastOptions: {
 | 
				
			||||||
 | 
					            adCTATextVast: true, // Enables getting CTA from the VAST tag
 | 
				
			||||||
 | 
					            adCTAText: 'CTA Text from fluid player vast options!', // Note: Will only show if VAST tag doesn't have TitleCTA
 | 
				
			||||||
 | 
					            adCTATextPosition: 'bottom left',
 | 
				
			||||||
 | 
					            adList: [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    roll: 'preRoll',
 | 
				
			||||||
 | 
					                    vastTag: '/static/vast_cta.xml', // VAST Tag with TitleCTA extension with CTA information
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,41 @@
 | 
				
			||||||
 | 
					<!doctype html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
 | 
					    <meta name="viewport"
 | 
				
			||||||
 | 
					          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
 | 
				
			||||||
 | 
					    <meta http-equiv="X-UA-Compatible" content="ie=edge">
 | 
				
			||||||
 | 
					    <title>VOD with CTA set from VAST with no friendly url</title>
 | 
				
			||||||
 | 
					    <%= htmlWebpackPlugin.tags.headTags %>
 | 
				
			||||||
 | 
					    <style lang="css">
 | 
				
			||||||
 | 
					        #fluid-player-e2e-case {
 | 
				
			||||||
 | 
					            width: 90%;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<video id="fluid-player-e2e-case">
 | 
				
			||||||
 | 
					    <source src="https://cdn.fluidplayer.com/videos/valerian-1080p.mkv" data-fluid-hd title="1080p" type="video/mp4"/>
 | 
				
			||||||
 | 
					    <source src="https://cdn.fluidplayer.com/videos/valerian-720p.mkv" data-fluid-hd title="720p" type="video/mp4"/>
 | 
				
			||||||
 | 
					    <source src="https://cdn.fluidplayer.com/videos/valerian-480p.mkv" title="480p" type="video/mp4"/>
 | 
				
			||||||
 | 
					</video>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<%= htmlWebpackPlugin.tags.bodyTags %>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    var instance = fluidPlayer('fluid-player-e2e-case', {
 | 
				
			||||||
 | 
					        vastOptions: {
 | 
				
			||||||
 | 
					            adCTATextVast: true, // Enables getting CTA from the VAST tag
 | 
				
			||||||
 | 
					            adList: [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    roll: 'preRoll',
 | 
				
			||||||
 | 
					                    vastTag: '/static/vast_cta_no_friendly_url.xml', // VAST Tag with TitleCTA extension with CTA information
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										43
									
								
								client/fluid-player/test/html/vod_basic_multiple.tpl.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,43 @@
 | 
				
			||||||
 | 
					<!doctype html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
 | 
					    <meta name="viewport"
 | 
				
			||||||
 | 
					          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
 | 
				
			||||||
 | 
					    <meta http-equiv="X-UA-Compatible" content="ie=edge">
 | 
				
			||||||
 | 
					    <title>VOD two players</title>
 | 
				
			||||||
 | 
					    <%= htmlWebpackPlugin.tags.headTags %>
 | 
				
			||||||
 | 
					    <style lang="css">
 | 
				
			||||||
 | 
					        #fluid-player-e2e-case-1 {
 | 
				
			||||||
 | 
					            width: 90%;
 | 
				
			||||||
 | 
					            height: 20vh;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        #fluid-player-e2e-case-2 {
 | 
				
			||||||
 | 
					            width: 90%;
 | 
				
			||||||
 | 
					            height: 20vh;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<video id="fluid-player-e2e-case-1">
 | 
				
			||||||
 | 
					    <source src="https://cdn.fluidplayer.com/videos/valerian-1080p.mkv" data-fluid-hd title="1080p" type="video/mp4"/>
 | 
				
			||||||
 | 
					    <source src="https://cdn.fluidplayer.com/videos/valerian-720p.mkv" data-fluid-hd title="720p" type="video/mp4"/>
 | 
				
			||||||
 | 
					    <source src="https://cdn.fluidplayer.com/videos/valerian-480p.mkv" title="480p" type="video/mp4"/>
 | 
				
			||||||
 | 
					</video>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<video id="fluid-player-e2e-case-2">
 | 
				
			||||||
 | 
					    <source src="https://cdn.fluidplayer.com/videos/valerian-1080p.mkv" data-fluid-hd title="1080p" type="video/mp4"/>
 | 
				
			||||||
 | 
					    <source src="https://cdn.fluidplayer.com/videos/valerian-720p.mkv" data-fluid-hd title="720p" type="video/mp4"/>
 | 
				
			||||||
 | 
					    <source src="https://cdn.fluidplayer.com/videos/valerian-480p.mkv" title="480p" type="video/mp4"/>
 | 
				
			||||||
 | 
					</video>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<%= htmlWebpackPlugin.tags.bodyTags %>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    var firstInstance = fluidPlayer('fluid-player-e2e-case-1');
 | 
				
			||||||
 | 
					    var secondInstance = fluidPlayer('fluid-player-e2e-case-2');
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										35
									
								
								client/fluid-player/test/html/vod_basic_subtitles.tpl.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,35 @@
 | 
				
			||||||
 | 
					<!doctype html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
 | 
					    <meta name="viewport"
 | 
				
			||||||
 | 
					          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
 | 
				
			||||||
 | 
					    <meta http-equiv="X-UA-Compatible" content="ie=edge">
 | 
				
			||||||
 | 
					    <title>VOD with subtitles</title>
 | 
				
			||||||
 | 
					    <%= htmlWebpackPlugin.tags.headTags %>
 | 
				
			||||||
 | 
					    <style lang="css">
 | 
				
			||||||
 | 
					        #fluid-player-e2e-case {
 | 
				
			||||||
 | 
					            width: 90%;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<video id="fluid-player-e2e-case">
 | 
				
			||||||
 | 
					    <source src="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4" type="video/mp4"/>
 | 
				
			||||||
 | 
					    <track label="English" kind="metadata" srclang="en" src="/static/subtitles/english.vtt" default>
 | 
				
			||||||
 | 
					    <track label="Deutsch" kind="metadata" srclang="de" src="/static/subtitles/deutsch.vtt">
 | 
				
			||||||
 | 
					</video>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<%= htmlWebpackPlugin.tags.bodyTags %>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    var instance = fluidPlayer('fluid-player-e2e-case', {
 | 
				
			||||||
 | 
					        layoutControls: {
 | 
				
			||||||
 | 
					            subtitlesEnabled: true
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										34
									
								
								client/fluid-player/test/html/vod_basic_vr.tpl.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,34 @@
 | 
				
			||||||
 | 
					<!doctype html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
 | 
					    <meta name="viewport"
 | 
				
			||||||
 | 
					          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
 | 
				
			||||||
 | 
					    <meta http-equiv="X-UA-Compatible" content="ie=edge">
 | 
				
			||||||
 | 
					    <title>VOD VR</title>
 | 
				
			||||||
 | 
					    <%= htmlWebpackPlugin.tags.headTags %>
 | 
				
			||||||
 | 
					    <style lang="css">
 | 
				
			||||||
 | 
					        #fluid-player-e2e-case {
 | 
				
			||||||
 | 
					            width: 90%;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<video id="fluid-player-e2e-case" crossorigin="anonymous">
 | 
				
			||||||
 | 
					    <source src="https://pchen66.github.io/Panolens/examples/asset/textures/video/ClashofClans.mp4" title="VR" type="video/mp4"/>
 | 
				
			||||||
 | 
					</video>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<%= htmlWebpackPlugin.tags.bodyTags %>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    var instance = fluidPlayer('fluid-player-e2e-case', {
 | 
				
			||||||
 | 
					        layoutControls: {
 | 
				
			||||||
 | 
					            showCardBoardView: true,
 | 
				
			||||||
 | 
					            showCardBoardJoystick: true
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										35
									
								
								client/fluid-player/test/html/vod_basic_vr_autoplay.tpl.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,35 @@
 | 
				
			||||||
 | 
					<!doctype html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
 | 
					    <meta name="viewport"
 | 
				
			||||||
 | 
					          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
 | 
				
			||||||
 | 
					    <meta http-equiv="X-UA-Compatible" content="ie=edge">
 | 
				
			||||||
 | 
					    <title>VOD VR with Auto Play</title>
 | 
				
			||||||
 | 
					    <%= htmlWebpackPlugin.tags.headTags %>
 | 
				
			||||||
 | 
					    <style lang="css">
 | 
				
			||||||
 | 
					        #fluid-player-e2e-case {
 | 
				
			||||||
 | 
					            width: 90%;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<video id="fluid-player-e2e-case" crossorigin="anonymous">
 | 
				
			||||||
 | 
					    <source src="https://pchen66.github.io/Panolens/examples/asset/textures/video/ClashofClans.mp4" title="VR" type="video/mp4"/>
 | 
				
			||||||
 | 
					</video>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<%= htmlWebpackPlugin.tags.bodyTags %>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    var instance = fluidPlayer('fluid-player-e2e-case', {
 | 
				
			||||||
 | 
					        layoutControls: {
 | 
				
			||||||
 | 
					            showCardBoardView: true,
 | 
				
			||||||
 | 
					            showCardBoardJoystick: true,
 | 
				
			||||||
 | 
					            autoPlay: true
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										38
									
								
								client/fluid-player/test/html/vod_basic_vtt.tpl.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,38 @@
 | 
				
			||||||
 | 
					<!doctype html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
 | 
					    <meta name="viewport"
 | 
				
			||||||
 | 
					          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
 | 
				
			||||||
 | 
					    <meta http-equiv="X-UA-Compatible" content="ie=edge">
 | 
				
			||||||
 | 
					    <title>VOD with timeline thumbnails</title>
 | 
				
			||||||
 | 
					    <%= htmlWebpackPlugin.tags.headTags %>
 | 
				
			||||||
 | 
					    <style lang="css">
 | 
				
			||||||
 | 
					        #fluid-player-e2e-case {
 | 
				
			||||||
 | 
					            width: 90%;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<video id="fluid-player-e2e-case">
 | 
				
			||||||
 | 
					    <source src="https://cdn.fluidplayer.com/videos/valerian-1080p.mkv" data-fluid-hd title="1080p" type="video/mp4"/>
 | 
				
			||||||
 | 
					    <source src="https://cdn.fluidplayer.com/videos/valerian-720p.mkv" data-fluid-hd title="720p" type="video/mp4"/>
 | 
				
			||||||
 | 
					    <source src="https://cdn.fluidplayer.com/videos/valerian-480p.mkv" title="480p" type="video/mp4"/>
 | 
				
			||||||
 | 
					</video>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<%= htmlWebpackPlugin.tags.bodyTags %>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    var instance = fluidPlayer('fluid-player-e2e-case', {
 | 
				
			||||||
 | 
					        layoutControls: {
 | 
				
			||||||
 | 
					            timelinePreview: {
 | 
				
			||||||
 | 
					                file: '/static/thumbnails.vtt',
 | 
				
			||||||
 | 
					                type: 'VTT'
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										1857
									
								
								client/fluid-player/test/html/vod_basic_vtt_static.tpl.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										72
									
								
								client/fluid-player/test/html/vod_event_api.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,72 @@
 | 
				
			||||||
 | 
					<!doctype html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
 | 
					    <meta content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
 | 
				
			||||||
 | 
					          name="viewport">
 | 
				
			||||||
 | 
					    <meta content="ie=edge" http-equiv="X-UA-Compatible">
 | 
				
			||||||
 | 
					    <title>VOD with Event API callbacks</title>
 | 
				
			||||||
 | 
					    <%= htmlWebpackPlugin.tags.headTags %>
 | 
				
			||||||
 | 
					    <style lang="css">
 | 
				
			||||||
 | 
					        #fluid-player-e2e-case {
 | 
				
			||||||
 | 
					            width: 90%;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<video id="fluid-player-e2e-case">
 | 
				
			||||||
 | 
					    <source src="https://cdn.fluidplayer.com/videos/valerian-1080p.mkv" data-fluid-hd title="1080p" type="video/mp4"/>
 | 
				
			||||||
 | 
					    <source src="https://cdn.fluidplayer.com/videos/valerian-720p.mkv" data-fluid-hd title="720p" type="video/mp4"/>
 | 
				
			||||||
 | 
					    <source src="https://cdn.fluidplayer.com/videos/valerian-480p.mkv" title="480p" type="video/mp4"/>
 | 
				
			||||||
 | 
					</video>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<p>Check your console for the messages beginning with <strong><i>type</i>Callback</strong></p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<%= htmlWebpackPlugin.tags.bodyTags %>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    var instance = fluidPlayer('fluid-player-e2e-case', {
 | 
				
			||||||
 | 
					        vastOptions: {
 | 
				
			||||||
 | 
					            allowVPAID: true, // Default false.
 | 
				
			||||||
 | 
					            adList: [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    roll: 'preRoll',
 | 
				
			||||||
 | 
					                    vastTag: '/static/vast_linear.xml',
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    roll: 'midRoll',
 | 
				
			||||||
 | 
					                    vastTag: '/static/vast_linear.xml',
 | 
				
			||||||
 | 
					                    timer: '25%',
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    roll: 'midRoll',
 | 
				
			||||||
 | 
					                    vastTag: '/static/vast_linear.xml',
 | 
				
			||||||
 | 
					                    timer: 50,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    roll: 'midRoll',
 | 
				
			||||||
 | 
					                    vastTag: '/static/vast_linear.xml',
 | 
				
			||||||
 | 
					                    timer: '75.0%',
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    roll: 'postRoll',
 | 
				
			||||||
 | 
					                    vastTag: '/static/vast_linear.xml',
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    instance.on('play', (...args) => console.log('playCallback', ...args));
 | 
				
			||||||
 | 
					    instance.on('seeked', (...args) => console.log('seekedCallback', ...args));
 | 
				
			||||||
 | 
					    instance.on('playing', (...args) => console.log('playingCallback', ...args));
 | 
				
			||||||
 | 
					    instance.on('theatreModeOn', (...args) => console.log('theatreModeOnCallback', ...args));
 | 
				
			||||||
 | 
					    instance.on('theatreModeOff', (...args) => console.log('theatreModeOffCallback', ...args));
 | 
				
			||||||
 | 
					    instance.on('timeupdate', (...args) => console.log('timeupdateCallback', ...args));
 | 
				
			||||||
 | 
					    instance.on('ended', (...args) => console.log('endedCallback', ...args));
 | 
				
			||||||
 | 
					    instance.on('pause', (...args) => console.log('pauseCallback', ...args));
 | 
				
			||||||
 | 
					    instance.on('miniPlayerToggle', (...args) => console.log('miniPlayerToggleCallback', ...args));
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										75
									
								
								client/fluid-player/test/html/vod_extended.tpl.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,75 @@
 | 
				
			||||||
 | 
					<!doctype html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
 | 
					    <meta name="viewport"
 | 
				
			||||||
 | 
					          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
 | 
				
			||||||
 | 
					    <meta http-equiv="X-UA-Compatible" content="ie=edge">
 | 
				
			||||||
 | 
					    <title>VOD extended</title>
 | 
				
			||||||
 | 
					    <%= htmlWebpackPlugin.tags.headTags %>
 | 
				
			||||||
 | 
					    <style lang="css">
 | 
				
			||||||
 | 
					        #fluid-player-e2e-case {
 | 
				
			||||||
 | 
					            width: 90%;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<video id="fluid-player-e2e-case">
 | 
				
			||||||
 | 
					    <source src="https://cdn.fluidplayer.com/videos/valerian-1080p.mkv" data-fluid-hd title="1080p" type="video/mp4"/>
 | 
				
			||||||
 | 
					    <source src="https://cdn.fluidplayer.com/videos/valerian-720p.mkv" data-fluid-hd title="720p" type="video/mp4"/>
 | 
				
			||||||
 | 
					    <source src="https://cdn.fluidplayer.com/videos/valerian-480p.mkv" title="480p" type="video/mp4"/>
 | 
				
			||||||
 | 
					</video>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<%= htmlWebpackPlugin.tags.bodyTags %>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    var instance = fluidPlayer('fluid-player-e2e-case', {
 | 
				
			||||||
 | 
					        layoutControls: {
 | 
				
			||||||
 | 
					            primaryColor: "#dd2e44",
 | 
				
			||||||
 | 
					            timelinePreview: {
 | 
				
			||||||
 | 
					                file: '/static/thumbnails.vtt',
 | 
				
			||||||
 | 
					                type: 'VTT'
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            logo: {
 | 
				
			||||||
 | 
					                imageUrl: '/static/logo.png',
 | 
				
			||||||
 | 
					                position: 'top right',
 | 
				
			||||||
 | 
					                clickUrl: 'https://fluidplayer.com',
 | 
				
			||||||
 | 
					                opacity: 1,
 | 
				
			||||||
 | 
					                mouseOverImageUrl: null,
 | 
				
			||||||
 | 
					                imageMargin: '2px',
 | 
				
			||||||
 | 
					                hideWithControls: true,
 | 
				
			||||||
 | 
					                showOverAds: true
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            fillToControls: true,
 | 
				
			||||||
 | 
					            posterImage: '/static/video-thumbnail.jpg',
 | 
				
			||||||
 | 
					            playbackRateEnabled: true,
 | 
				
			||||||
 | 
					            allowDownload: true,
 | 
				
			||||||
 | 
					            autoPlay: false,
 | 
				
			||||||
 | 
					            playButtonShowing: true,
 | 
				
			||||||
 | 
					            playPauseAnimation: true,
 | 
				
			||||||
 | 
					            htmlOnPauseBlock: {
 | 
				
			||||||
 | 
					                html: '<button>hello world</button>',
 | 
				
			||||||
 | 
					                height: 100,
 | 
				
			||||||
 | 
					                width: 200
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        vastOptions: {
 | 
				
			||||||
 | 
					            allowVPAID: true, // Default false.
 | 
				
			||||||
 | 
					            adList: [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    roll: 'preRoll',
 | 
				
			||||||
 | 
					                    vastTag: '/static/vpaid_linear.xml'
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    roll: 'midRoll',
 | 
				
			||||||
 | 
					                    vastTag: '/static/vpaid_nonlinear.xml',
 | 
				
			||||||
 | 
					                    timer: 4
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										49
									
								
								client/fluid-player/test/html/vod_live_ad.tpl.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,49 @@
 | 
				
			||||||
 | 
					<!doctype html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
 | 
					    <meta name="viewport"
 | 
				
			||||||
 | 
					          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
 | 
				
			||||||
 | 
					    <meta http-equiv="X-UA-Compatible" content="ie=edge">
 | 
				
			||||||
 | 
					    <title>VOD with VAST Live Ad Creative</title>
 | 
				
			||||||
 | 
					    <%= htmlWebpackPlugin.tags.headTags %>
 | 
				
			||||||
 | 
					    <style lang="css">
 | 
				
			||||||
 | 
					        #fluid-player-e2e-case {
 | 
				
			||||||
 | 
					            width: 90%;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<video id="fluid-player-e2e-case">
 | 
				
			||||||
 | 
					    <source src="https://cdn.fluidplayer.com/videos/valerian-1080p.mkv" data-fluid-hd title="1080p" type="video/mp4"/>
 | 
				
			||||||
 | 
					    <source src="https://cdn.fluidplayer.com/videos/valerian-720p.mkv" data-fluid-hd title="720p" type="video/mp4"/>
 | 
				
			||||||
 | 
					    <source src="https://cdn.fluidplayer.com/videos/valerian-480p.mkv" title="480p" type="video/mp4"/>
 | 
				
			||||||
 | 
					</video>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<%= htmlWebpackPlugin.tags.bodyTags %>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    var instance = fluidPlayer('fluid-player-e2e-case',{
 | 
				
			||||||
 | 
					        vastOptions: {
 | 
				
			||||||
 | 
					            skipButtonCaption: 'Wait [seconds] please.',
 | 
				
			||||||
 | 
					            skipButtonClickCaption: 'Click to skip now',
 | 
				
			||||||
 | 
					            vastTimeout: 5000,
 | 
				
			||||||
 | 
					            showPlayButton: true,
 | 
				
			||||||
 | 
					            adList: [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    roll: 'preRoll',
 | 
				
			||||||
 | 
					                    vastTag: '/static/vast_hls.xml',
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    roll: 'midRoll',
 | 
				
			||||||
 | 
					                    vastTag: '/static/vast_hls.xml',
 | 
				
			||||||
 | 
					                    timer: 15
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										136
									
								
								client/fluid-player/test/html/vod_miniplayer.tpl.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,136 @@
 | 
				
			||||||
 | 
					<!doctype html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
 | 
					    <meta name="viewport"
 | 
				
			||||||
 | 
					          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
 | 
				
			||||||
 | 
					    <meta http-equiv="X-UA-Compatible" content="ie=edge">
 | 
				
			||||||
 | 
					    <title>VOD with Mini Player</title>
 | 
				
			||||||
 | 
					    <%= htmlWebpackPlugin.tags.headTags %>
 | 
				
			||||||
 | 
					    <style lang="css">
 | 
				
			||||||
 | 
					        #fluid-player-e2e-case { width: 90%; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        body { height: 300vh; background: rgb(131,58,180); background: linear-gradient(0deg, rgba(131,58,180,1) 0%, rgba(253,29,29,1) 50%, rgba(252,176,69,1) 100%); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        #setupForm { display: flex; align-items: flex-start; flex-direction: column; }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<%= htmlWebpackPlugin.tags.bodyTags %>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div id="setupForm">
 | 
				
			||||||
 | 
					    <div><input type="checkbox" name="enabled" id="enabled" checked><label for="enabled">Enabled</label></div>
 | 
				
			||||||
 | 
					    <div><input type="checkbox" name="autoPlay" id="autoPlay" checked><label for="autoPlay">Auto Play</label></div>
 | 
				
			||||||
 | 
					    <div><input type="checkbox" name="vastad" id="vastad" checked><label for="vastad">VAST Ad</label></div>
 | 
				
			||||||
 | 
					    <div><input type="checkbox" name="autoToggle" id="autoToggle" checked><label for="autoToggle">Auto Toggle</label></div>
 | 
				
			||||||
 | 
					    <div><input type="number" name="width" id="width" value="400"><label for="width">Width</label></div>
 | 
				
			||||||
 | 
					    <div><input type="number" name="height" id="height" value="225"><label for="height">Height</label></div>
 | 
				
			||||||
 | 
					    <div><input type="number" name="widthMobile" id="widthMobile" value="50"><label for="widthMobile">Width Mobile</label></div>
 | 
				
			||||||
 | 
					    <div><input type="text" name="placeholderText" id="placeholderText" value="Playing in Miniplayer"><label for="placeholderText">Placeholder Text</label></div>
 | 
				
			||||||
 | 
					    <div><input type="text" name="position" id="position" value="bottom right"><label for="position">Position (top left, top right, bottom left, bottom right)</label></div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <p><button id="setupPlayer">Show Player</button></p>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div id="mountingPoint"></div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    document.getElementById('setupPlayer').addEventListener('click', () => {
 | 
				
			||||||
 | 
					        const enabled = document.getElementById('enabled').checked;
 | 
				
			||||||
 | 
					        const autoPlay = document.getElementById('autoPlay').checked;
 | 
				
			||||||
 | 
					        const vastad = document.getElementById('vastad').checked;
 | 
				
			||||||
 | 
					        const autoToggle = document.getElementById('autoToggle').checked;
 | 
				
			||||||
 | 
					        const width = document.getElementById('width').value;
 | 
				
			||||||
 | 
					        const height = document.getElementById('height').value;
 | 
				
			||||||
 | 
					        const widthMobile = document.getElementById('widthMobile').value;
 | 
				
			||||||
 | 
					        const placeholderText = document.getElementById('placeholderText').value;
 | 
				
			||||||
 | 
					        const position = document.getElementById('position').value;
 | 
				
			||||||
 | 
					        const mountingPoint = document.getElementById('mountingPoint');
 | 
				
			||||||
 | 
					        mountingPoint.innerHTML = `
 | 
				
			||||||
 | 
					            <video id="fluid-player-e2e-case">
 | 
				
			||||||
 | 
					                <source src="https://cdn.fluidplayer.com/videos/valerian-1080p.mkv" data-fluid-hd title="1080p" type="video/mp4"/>
 | 
				
			||||||
 | 
					                <source src="https://cdn.fluidplayer.com/videos/valerian-720p.mkv" data-fluid-hd title="720p" type="video/mp4"/>
 | 
				
			||||||
 | 
					                <source src="https://cdn.fluidplayer.com/videos/valerian-480p.mkv" title="480p" type="video/mp4"/>
 | 
				
			||||||
 | 
					            </video>
 | 
				
			||||||
 | 
					        `;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const instance = fluidPlayer('fluid-player-e2e-case', {
 | 
				
			||||||
 | 
					            layoutControls: {
 | 
				
			||||||
 | 
					                autoPlay,
 | 
				
			||||||
 | 
					                miniPlayer: {
 | 
				
			||||||
 | 
					                    enabled,
 | 
				
			||||||
 | 
					                    width: Number(width),
 | 
				
			||||||
 | 
					                    height: Number(height),
 | 
				
			||||||
 | 
					                    widthMobile: Number(widthMobile),
 | 
				
			||||||
 | 
					                    placeholderText,
 | 
				
			||||||
 | 
					                    position,
 | 
				
			||||||
 | 
					                    autoToggle
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            ...(vastad ? {
 | 
				
			||||||
 | 
					                vastOptions: {
 | 
				
			||||||
 | 
					                    allowVPAID: true,
 | 
				
			||||||
 | 
					                    adText: 'Advertising helps us keep the lights on', // Default null,
 | 
				
			||||||
 | 
					                    adTextPosition: 'top left', // Default 'top left
 | 
				
			||||||
 | 
					                    adCTAText: 'Subscribe now!', // Default "Visit now!",
 | 
				
			||||||
 | 
					                    adCTATextPosition: 'top right', //Default 'bottom right’,
 | 
				
			||||||
 | 
					                    adCTATextVast: true, // Enabled. To use the CTA text as provided in the VAST XML.
 | 
				
			||||||
 | 
					                    adList: [
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            roll: 'preRoll',
 | 
				
			||||||
 | 
					                            vastTag: '/static/vast_linear.xml',
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            roll: 'midRoll',
 | 
				
			||||||
 | 
					                            vastTag: '/static/vast_nonlinear.xml',
 | 
				
			||||||
 | 
					                            timer: 0
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            roll: 'midRoll',
 | 
				
			||||||
 | 
					                            vastTag: '/static/vpaid_linear.xml',
 | 
				
			||||||
 | 
					                            timer: 10
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            roll: 'midRoll',
 | 
				
			||||||
 | 
					                            vastTag: '/static/vpaid_nonlinear.xml',
 | 
				
			||||||
 | 
					                            timer: 15
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    ]
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            } : {})
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        console.log('%cTEST INSTANCE', 'color: #fff; font-weight: bold; background-color: #BADA55; padding: 3px 6px; border-radius: 3px;', instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        instance.on('miniPlayerToggle', (event) => console.log(`[Event API] miniPlayerToggle`, event, event.detail.isToggledOn));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Array.from(document.getElementById('setupForm').children).forEach(child => child.remove());
 | 
				
			||||||
 | 
					        const refreshPage = document.createElement('button');
 | 
				
			||||||
 | 
					        refreshPage.onclick = () => location.reload();
 | 
				
			||||||
 | 
					        refreshPage.innerText = 'Reset Test';
 | 
				
			||||||
 | 
					        refreshPage.style.marginBottom = '5px';
 | 
				
			||||||
 | 
					        document.getElementById('setupForm').appendChild(refreshPage);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const toggleMiniPlayer = document.createElement('button');
 | 
				
			||||||
 | 
					        toggleMiniPlayer.onclick = () => instance.toggleMiniPlayer();
 | 
				
			||||||
 | 
					        toggleMiniPlayer.innerText = 'Toggle MiniPlayer (Controls API)';
 | 
				
			||||||
 | 
					        toggleMiniPlayer.style.marginBottom = '5px';
 | 
				
			||||||
 | 
					        document.getElementById('setupForm').appendChild(toggleMiniPlayer);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const toggleMiniPlayerOn = document.createElement('button');
 | 
				
			||||||
 | 
					        toggleMiniPlayerOn.onclick = () => instance.toggleMiniPlayer(true);
 | 
				
			||||||
 | 
					        toggleMiniPlayerOn.innerText = 'Toggle MiniPlayer ON (Controls API)';
 | 
				
			||||||
 | 
					        toggleMiniPlayerOn.style.marginBottom = '5px';
 | 
				
			||||||
 | 
					        document.getElementById('setupForm').appendChild(toggleMiniPlayerOn);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const toggleMiniPlayerOff = document.createElement('button');
 | 
				
			||||||
 | 
					        toggleMiniPlayerOff.onclick = () => instance.toggleMiniPlayer(false);
 | 
				
			||||||
 | 
					        toggleMiniPlayerOff.innerText = 'Toggle MiniPlayer OFF (Controls API)';
 | 
				
			||||||
 | 
					        toggleMiniPlayerOff.style.marginBottom = '5px';
 | 
				
			||||||
 | 
					        document.getElementById('setupForm').appendChild(toggleMiniPlayerOff);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||