This commit is contained in:
SA 2025-04-02 16:32:47 +02:00
parent b143186d85
commit 3a7b9b0a3b
490 changed files with 50195 additions and 7186 deletions

View File

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

View File

@ -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
View File

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

View File

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

View File

@ -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
View 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
View 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}

View 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;
}

View File

@ -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
View 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
View File

@ -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' {

View File

@ -0,0 +1,3 @@
{
"presets": ["@babel/preset-env"]
}

View 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

View 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.

View 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.

View 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.

View 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
View 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/

View File

@ -0,0 +1,9 @@
test
.idea
e2e
.editorconfig
yarn.lock
webpack.config.js
dist
dist-cdn
.github

View 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

View 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.

View 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.

View File

@ -0,0 +1,23 @@
# Fluid Player
[![Latest version](https://img.shields.io/github/v/release/fluid-player/fluid-player?include_prereleases&label=latest%20release&sort=semver&style=flat-square&logo=GitHub)](https://github.com/fluid-player/fluid-player/releases/latest)
[![npm](https://img.shields.io/npm/v/fluid-player?style=flat-square&logo=npm)](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).

View 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');
});
});

View 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
});
});

View 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;
}

View 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 });
});
});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 479 KiB

View 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

File diff suppressed because it is too large Load Diff

View 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"
}
}

View 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,
// },
});

View 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;
}
}

File diff suppressed because it is too large Load Diff

View 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;
}
}

File diff suppressed because it is too large Load Diff

222
client/fluid-player/src/index.d.ts vendored Normal file
View 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;
}>;
}

View 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;

File diff suppressed because it is too large Load Diff

View 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();
}
};
}

View 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;
}

View 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;
}
};
}

View 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();
});
};
}

View 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];
}
}

View 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;
};
}

View 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'));
};
});
}
}

File diff suppressed because it is too large Load Diff

View 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);
};
}

View 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();

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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 } }[];
}

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>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>

View 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>

View 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>

View 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>

View 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>

View 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>

View File

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

View 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>

View 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`

View 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>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>

View 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>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>

View 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>

View File

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

View 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>

View 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>

View 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>

View 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 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>

View File

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

View File

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

View 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>

View 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>

View 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>

View 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>

View 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>

File diff suppressed because it is too large Load Diff

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>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>

View 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>

View 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 - 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>

Some files were not shown because too many files have changed in this diff Show More