squash
|
@ -4,6 +4,7 @@ dist/
|
|||
client/dev/
|
||||
client/dist/
|
||||
client/node_modules/
|
||||
client/fluid-player/node_modules/
|
||||
__pycache__
|
||||
venv
|
||||
.env
|
||||
|
@ -20,3 +21,4 @@ docker-compose*
|
|||
node_modules
|
||||
npm-debug.log
|
||||
README.md
|
||||
.history
|
||||
|
|
4
.flake8
|
@ -6,6 +6,10 @@ ignore =
|
|||
F401
|
||||
# flake struggles with endpoints returning tuples on exceptions
|
||||
E722
|
||||
# apparently it's both an antipattern and a best practice:
|
||||
# https://www.flake8rules.com/rules/W503.html
|
||||
# just python things
|
||||
W503
|
||||
exclude =
|
||||
.git,
|
||||
__pycache__,
|
||||
|
|
4
.gitignore
vendored
|
@ -6,8 +6,6 @@ flask.cfg
|
|||
/config.py
|
||||
redis_map.py
|
||||
|
||||
# Dev only files
|
||||
test/
|
||||
.idea
|
||||
dev_*
|
||||
|
||||
|
@ -164,3 +162,5 @@ dmypy.json
|
|||
|
||||
# Cython debug symbols
|
||||
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
|
||||
|
||||
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
|
||||
FROM python:3.12
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apt-get update && apt-get install -y libpq-dev curl jq
|
||||
|
||||
COPY requirements.txt 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
|
||||
|
||||
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
|
||||
|
|
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}
|
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,167 +0,0 @@
|
|||
// @ts-check
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
|
||||
/**
|
||||
* @typedef IConfiguration
|
||||
* @property {string} site
|
||||
* @property {string} [sentry_dsn_js]
|
||||
* @property {boolean} development_mode
|
||||
* @property {boolean} automatic_migrations
|
||||
* @property {IServerConfig} webserver
|
||||
* @property {IArchiveServerConfig} [archive_server]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef IServerConfig
|
||||
* @property {IUIConfig} ui
|
||||
* @property {number} port
|
||||
* @property {string} [base_url]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef IUIConfig
|
||||
* @property {IHomeConfig} home
|
||||
* @property {{paysite_list: string[], artists_or_creators: string}} config
|
||||
* @property {IMatomoConfig} [matomo]
|
||||
* @property {ISidebarConfig} [sidebar]
|
||||
* @property {unknown[]} sidebar_items
|
||||
* @property {unknown[]} [footer_items]
|
||||
* @property {IBannerConfig} [banner]
|
||||
* @property {IAdsConfig} [ads]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef IMatomoConfig
|
||||
* @property {boolean} enabled
|
||||
* @property {string} plain_code b64-encoded string
|
||||
* @property {string} tracking_domain
|
||||
* @property {string} tracking_code
|
||||
* @property {string} site_id
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef ISidebarConfig
|
||||
* @property {boolean} [disable_dms]
|
||||
* @property {boolean} [disable_faq]
|
||||
* @property {boolean} [disable_filehaus]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef IBannerConfig
|
||||
* @property {string} [global] b64-encoded string
|
||||
* @property {string} [welcome] b64-encoded string
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef IHomeConfig
|
||||
* @property {string} [site_name]
|
||||
* @property {string} [mascot_path]
|
||||
* @property {string} [logo_path]
|
||||
* @property {string} [welcome_credits] b64-encoded string
|
||||
* @property {string} [home_background_image]
|
||||
* @property {{ title: string, date: string, content: string }[]} [announcements]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef IAdsConfig
|
||||
* @property {string} [header] b64-encoded string
|
||||
* @property {string} [middle] b64-encoded string
|
||||
* @property {string} [footer] b64-encoded string
|
||||
* @property {string} [slider] b64-encoded string
|
||||
* @property {string} [video] b64-encoded JSON string
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef IArchiveServerConfig
|
||||
* @property {boolean} [enabled]
|
||||
*/
|
||||
|
||||
const configuration = getConfiguration();
|
||||
const apiServerBaseURL = configuration.webserver.base_url;
|
||||
const sentryDSN = configuration.sentry_dsn_js;
|
||||
const apiServerPort = !apiServerBaseURL
|
||||
? undefined
|
||||
: configuration.webserver.port;
|
||||
const siteName = configuration.webserver.ui.home.site_name || "Kemono";
|
||||
const homeBackgroundImage =
|
||||
configuration.webserver.ui.home.home_background_image;
|
||||
const homeMascotPath = configuration.webserver.ui.home.mascot_path;
|
||||
const homeLogoPath = configuration.webserver.ui.home.logo_path;
|
||||
const homeWelcomeCredits = configuration.webserver.ui.home.welcome_credits;
|
||||
const homeAnnouncements = configuration.webserver.ui.home.announcements;
|
||||
// TODO: in development it should point to webpack server
|
||||
const kemonoSite = configuration.site || "http://localhost:5000";
|
||||
const paysiteList = configuration.webserver.ui.config.paysite_list;
|
||||
const artistsOrCreators =
|
||||
configuration.webserver.ui.config.artists_or_creators ?? "Artists";
|
||||
const disableDMs = configuration.webserver.ui.sidebar?.disable_dms ?? true;
|
||||
const disableFAQ = configuration.webserver.ui.sidebar?.disable_faq ?? true;
|
||||
const disableFilehaus =
|
||||
configuration.webserver.ui.sidebar?.disable_filehaus ?? true;
|
||||
const sidebarItems = configuration.webserver.ui.sidebar_items;
|
||||
const footerItems = configuration.webserver.ui.footer_items;
|
||||
const bannerGlobal = configuration.webserver.ui.banner?.global;
|
||||
const bannerWelcome = configuration.webserver.ui.banner?.welcome;
|
||||
const headerAd = configuration.webserver.ui.ads?.header;
|
||||
const middleAd = configuration.webserver.ui.ads?.middle;
|
||||
const footerAd = configuration.webserver.ui.ads?.footer;
|
||||
const sliderAd = configuration.webserver.ui.ads?.slider;
|
||||
const videoAd = configuration.webserver.ui.ads?.video;
|
||||
const isArchiveServerEnabled = configuration.archive_server?.enabled ?? false;
|
||||
const analyticsEnabled = configuration.webserver.ui.matomo?.enabled ?? false;
|
||||
const analyticsCode = configuration.webserver.ui.matomo?.plain_code;
|
||||
const iconsPrepend = process.env.ICONS_PREPEND || "";
|
||||
const bannersPrepend = process.env.BANNERS_PREPEND || "";
|
||||
const thumbnailsPrepend = process.env.THUMBNAILS_PREPEND || "";
|
||||
const creatorsLocation = process.env.CREATORS_LOCATION || "";
|
||||
|
||||
/**
|
||||
* @TODO config validation
|
||||
* @returns {IConfiguration}
|
||||
*/
|
||||
function getConfiguration() {
|
||||
const configPath = path.resolve(__dirname, "..", "..", "config.json");
|
||||
// TODO: async reading
|
||||
const fileContent = fs.readFileSync(configPath, { encoding: "utf8" });
|
||||
/**
|
||||
* @type {IConfiguration}
|
||||
*/
|
||||
const config = JSON.parse(fileContent);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
kemonoSite,
|
||||
sentryDSN,
|
||||
siteName,
|
||||
iconsPrepend,
|
||||
bannersPrepend,
|
||||
thumbnailsPrepend,
|
||||
creatorsLocation,
|
||||
artistsOrCreators,
|
||||
disableDMs,
|
||||
disableFAQ,
|
||||
disableFilehaus,
|
||||
sidebarItems,
|
||||
footerItems,
|
||||
bannerGlobal,
|
||||
bannerWelcome,
|
||||
homeBackgroundImage,
|
||||
homeMascotPath,
|
||||
homeLogoPath,
|
||||
paysiteList,
|
||||
homeWelcomeCredits,
|
||||
homeAnnouncements,
|
||||
headerAd,
|
||||
middleAd,
|
||||
footerAd,
|
||||
sliderAd,
|
||||
videoAd,
|
||||
isArchiveServerEnabled,
|
||||
apiServerBaseURL,
|
||||
apiServerPort,
|
||||
analyticsEnabled,
|
||||
analyticsCode,
|
||||
};
|
47
client/configs/vars.mjs
Normal file
|
@ -0,0 +1,47 @@
|
|||
// @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 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 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;
|
4
client/extra.d.ts
vendored
|
@ -1,7 +1,7 @@
|
|||
// required for typescript not to choke on css modules
|
||||
declare module '*.scss' {
|
||||
const content: Record<string, string>;
|
||||
export default content;
|
||||
const classes: { [key: string]: string };
|
||||
export = classes;
|
||||
}
|
||||
|
||||
declare module '*.yaml' {
|
||||
|
|
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>
|
69
client/fluid-player/test/html/vod_responsive.tpl.html
Normal file
|
@ -0,0 +1,69 @@
|
|||
<!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 responsive</title>
|
||||
<%= htmlWebpackPlugin.tags.headTags %>
|
||||
<style lang="css">
|
||||
#video-container {
|
||||
display: inline-block;
|
||||
width: 70%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#video-container.resized {
|
||||
width: 40%;
|
||||
}
|
||||
|
||||
#video-container #spacer {
|
||||
margin-top: 56.25%; // 16:9
|
||||
}
|
||||
|
||||
#video-container #video-wrapper {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: red;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<p>
|
||||
<button id="toggle-size">Toggle size</button>
|
||||
</p>
|
||||
</div>
|
||||
<div id="video-container">
|
||||
<div id="spacer"></div>
|
||||
<div id="video-wrapper">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= htmlWebpackPlugin.tags.bodyTags %>
|
||||
|
||||
<script>
|
||||
var videoContainer = document.getElementById('video-container');
|
||||
|
||||
document.getElementById('toggle-size').addEventListener('click', function() {
|
||||
videoContainer.classList.toggle('resized');
|
||||
});
|
||||
|
||||
var instance = fluidPlayer('fluid-player-e2e-case', {
|
||||
layoutControls: {
|
||||
fillToContainer: true
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
60
client/fluid-player/test/html/vod_vast__linear.html
Normal file
|
@ -0,0 +1,60 @@
|
|||
<!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 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="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: {
|
||||
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',
|
||||
},
|
||||
]
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
60
client/fluid-player/test/html/vod_vast__non_linear.html
Normal file
|
@ -0,0 +1,60 @@
|
|||
<!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 Non 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="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: {
|
||||
allowVPAID: true, // Default false.
|
||||
adList: [
|
||||
{
|
||||
roll: 'preRoll',
|
||||
vastTag: '/static/vast_nonlinear.xml',
|
||||
},
|
||||
{
|
||||
roll: 'midRoll',
|
||||
vastTag: '/static/vast_nonlinear.xml',
|
||||
timer: '25%',
|
||||
},
|
||||
{
|
||||
roll: 'midRoll',
|
||||
vastTag: '/static/vast_nonlinear.xml',
|
||||
timer: 50,
|
||||
},
|
||||
{
|
||||
roll: 'midRoll',
|
||||
vastTag: '/static/vast_nonlinear.xml',
|
||||
timer: '75.0%',
|
||||
},
|
||||
{
|
||||
roll: 'postRoll',
|
||||
vastTag: '/static/vast_nonlinear.xml',
|
||||
},
|
||||
]
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
42
client/fluid-player/test/html/vod_vast_ad_buffet.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>VOD with VAST Ad Buffet</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: {
|
||||
allowVPAID: true, // Default false.
|
||||
adCTATextVast: true,
|
||||
adList: [
|
||||
{
|
||||
roll: 'preRoll',
|
||||
vastTag: '/static/vast_ad_buffet.xml',
|
||||
},
|
||||
]
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -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>VOD with VAST Ad Buffet With Error</title>
|
||||
<%= htmlWebpackPlugin.tags.headTags %>
|
||||
<style lang="css">
|
||||
#fluid-player-e2e-case {
|
||||
width: 90%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<p>First AD doesn't have a Creative, fallbacks to second ad.</p>
|
||||
|
||||
<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: {
|
||||
allowVPAID: true, // Default false.
|
||||
adCTATextVast: true,
|
||||
adList: [
|
||||
{
|
||||
roll: 'preRoll',
|
||||
vastTag: '/static/vast_ad_buffet_with_error.xml',
|
||||
},
|
||||
]
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
42
client/fluid-player/test/html/vod_vast_ad_pod.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>VOD with VAST Ad Pod</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: {
|
||||
allowVPAID: true, // Default false.
|
||||
adCTATextVast: true,
|
||||
adList: [
|
||||
{
|
||||
roll: 'preRoll',
|
||||
vastTag: '/static/vast_ad_pod.xml',
|
||||
},
|
||||
]
|
||||
}
|
||||
});
|
||||
</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 VAST Ad Pod - Loaded from a VAST Wrapper</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: {
|
||||
allowVPAID: true, // Default false.
|
||||
adCTATextVast: true,
|
||||
adList: [
|
||||
{
|
||||
roll: 'preRoll',
|
||||
vastTag: '/static/vast_wrapper_to_ad_pod.xml',
|
||||
},
|
||||
]
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|