squash
|
@ -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>
|