@hubspot/video-player-core 0.1.19 → 0.1.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/components/TranslationsProvider.d.ts +1 -1
- package/dist/components/VideoFetchProvider.d.ts +1 -1
- package/dist/components/VideoPlayer.d.ts +0 -1
- package/dist/components/VideoPlayerProvider.d.ts +1 -1
- package/dist/index.cjs +2470 -0
- package/dist/index.js +55661 -10
- package/dist/styles/player-theme-styles.d.ts +3 -0
- package/dist/styles/player-theme-styles.d.ts.map +1 -0
- package/package.json +11 -9
- package/dist/api/utils.js +0 -8
- package/dist/api/video.js +0 -22
- package/dist/components/CaptionTracks.js +0 -11
- package/dist/components/HSPlayerIcons.js +0 -14
- package/dist/components/HSThemeTemplate.js +0 -94
- package/dist/components/TranslationsProvider.js +0 -28
- package/dist/components/VideoFetchProvider.js +0 -12
- package/dist/components/VideoPlayer.js +0 -51
- package/dist/components/VideoPlayerProvider.js +0 -162
- package/dist/constants.js +0 -8
- package/dist/hooks/useAsyncEffect.js +0 -54
- package/dist/hooks/useSubtitles.js +0 -17
- package/dist/hooks/useVideo.js +0 -17
- package/dist/styles/mux-player.css +0 -23
- package/dist/styles/player-theme.css +0 -446
- package/dist/types.js +0 -1
- package/dist/utils/i18n.js +0 -79
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const _default: ":host {\n direction: ltr;\n}\n\nmedia-controller {\n --media-font-family: 'Lexend Deca', Helvetica, Arial, sans-serif;\n --media-font-weight: 500;\n --media-font-size: 12px;\n --hs-fullscreen-font-size: 17px;\n --hs-tooltip-font-size: 12px;\n font-family: var(--media-font-family);\n font-weight: var(--media-font-weight);\n font-size: var(--media-font-size);\n -webkit-font-smoothing: antialiased;\n\n --media-primary-color: #ffffff;\n --media-text-color: #ffffff;\n\n --hs-secondary-color: #4b4b4b;\n --hs-secondary-color-translucent: #4b4b4bcc;\n --media-secondary-color: transparent;\n\n --media-accent-color: #f7761f;\n --hs-menu-border-color: #aaa;\n --hs-menu-bottom-offset: 40px;\n\n --media-menu-background: var(--hs-secondary-color);\n --media-control-hover-background: transparent;\n --media-range-track-height: 3px;\n --media-range-thumb-height: 13px;\n --media-range-thumb-width: 13px;\n --media-range-thumb-border-radius: 13px;\n --media-tooltip-background-color: var(\n --hs-secondary-color,\n rgba(20, 20, 30, 0.7)\n );\n --media-object-fit: cover; /* avoids 1px black bars that tend to occur in safari, depending on exact window & video size */\n position: absolute;\n height: 100%;\n width: 100%;\n}\n\nmedia-controller[breakpointmd] {\n --media-font-size: 14px;\n --hs-menu-bottom-offset: 50px;\n}\n\n/* The biggest size controller is tied to going fullscreen instead of a player width */\nmedia-controller[mediaisfullscreen] {\n font-size: var(--hs-fullscreen-font-size);\n --media-range-thumb-height: 20px;\n --media-range-thumb-width: 20px;\n --media-range-thumb-border-radius: 10px;\n --media-range-track-height: 4px;\n}\n/* attempt to move captions above the control bar, not working except for Chrome, see https://www.mux.com/blog/if-you-can-read-this-your-browser-captions-are-broken */\nvideo::-webkit-media-text-track-display {\n transform: translateY(-3em);\n}\n\n.hs-button {\n position: relative;\n display: inline-block;\n width: 36px;\n padding: 0 2px;\n height: 100%;\n opacity: 0.9;\n transition: opacity 0.1s cubic-bezier(0.4, 0, 1, 1);\n --media-font-size: var(--hs-tooltip-font-size);\n}\n[breakpointmd] .hs-button {\n width: 42px;\n}\n[mediaisfullscreen] .hs-button {\n width: 54px;\n --hs-tooltip-font-size: 14px;\n}\n\n.hs-button svg {\n height: 100%;\n width: 100%;\n fill: var(--media-primary-color);\n fill-rule: evenodd;\n}\n\n.svg-shadow {\n stroke: #000;\n stroke-opacity: 0.15;\n stroke-width: 2px;\n fill: none;\n}\n\n/* Big centered Play/Pause */\nmedia-play-button.centered-play-button,\nmedia-pause-button.centered-play-button {\n position: absolute;\n display: flex;\n justify-content: center;\n align-items: center;\n height: 25%;\n aspect-ratio: 1;\n padding: 0;\n z-index: 1;\n border-radius: 50%;\n opacity: 0;\n --media-control-background: var(--media-accent-color);\n --media-control-hover-background: var(--media-accent-color);\n --media-tooltip-display: none;\n}\n[mediapaused] media-play-button.centered-play-button {\n opacity: 1;\n transition: opacity 0.3s ease-in;\n}\n.centered-play-button svg {\n position: absolute;\n height: 80%;\n fill: var(--hs-secondary-color);\n}\n\n/* Error Dialog */\nmedia-error-dialog {\n --media-control-background: rgba(255, 255, 255, 0.7);\n z-index: 2;\n}\n\n/* Gradient */\n.hs-control-bar-background {\n position: absolute;\n display: flex;\n flex-direction: column;\n left: 10px;\n right: 10px;\n bottom: 10px;\n background-color: var(--hs-secondary-color-translucent);\n border-radius: 5px;\n padding-top: 3px;\n}\n.hs-bottom-gradient {\n position: absolute;\n width: 100%;\n height: 100%;\n bottom: 0;\n pointer-events: none;\n box-shadow: inset 0 -24px 8px -8px rgba(30, 30, 30, 0.3),\n inset 0 -48px 16px -16px rgba(60, 60, 60, 0.25);\n border-radius: 5px;\n}\n[data-theme-variant='light'].hs-bottom-gradient {\n box-shadow: inset 0 -24px 8px -8px rgba(210, 210, 210, 0.3),\n inset 0 -48px 16px -16px rgba(250, 250, 250, 0.25);\n}\n\n/* Settings Menu - media-settings-menu and media-captions menu are at bottom, or else they cannot be clicked and the player toggles play/pause instead */\nmedia-settings-menu {\n position: absolute;\n background-color: var(--hs-secondary-color-translucent);\n border: 1px solid var(--hs-menu-border-color);\n border-radius: 5px;\n right: 12px;\n bottom: var(--hs-menu-bottom-offset);\n z-index: 10;\n will-change: width, height;\n transition: opacity 0.1s cubic-bezier(0, 0, 0.2, 1);\n user-select: none;\n --media-settings-menu-min-width: 220px;\n}\n[mediaisfullscreen] media-settings-menu {\n --media-settings-menu-min-width: 320px;\n right: 24px;\n bottom: 70px;\n}\nmedia-settings-menu-item {\n height: 40px;\n font-weight: var(--media-font-weight);\n padding-top: 0;\n padding-bottom: 0;\n}\n\n[mediaisfullscreen] media-settings-menu-item {\n font-size: var(--hs-fullscreen-font-size);\n height: 50px;\n}\n\nmedia-settings-menu-item[submenusize='0'] {\n display: none;\n}\n\n/* Also hide if only 'Auto' is added. */\n.quality-settings[submenusize='1'] {\n display: none;\n}\n\nmedia-captions-menu {\n position: absolute;\n background-color: var(--hs-secondary-color-translucent);\n border: 1px solid var(--hs-menu-border-color);\n border-radius: 5px;\n bottom: var(--hs-menu-bottom-offset);\n right: 70px;\n z-index: 10;\n will-change: width, height;\n transition: opacity 0.1s cubic-bezier(0, 0, 0.2, 1);\n user-select: none;\n}\n[mediaisfullscreen] media-captions-menu {\n bottom: var(--hs-menu-bottom-offset);\n font-size: var(--hs-fullscreen-font-size);\n}\n\n/* Time Range / Progress Bar */\nmedia-time-range {\n width: calc(100% + 10px);\n height: 8px;\n left: -5px;\n z-index: 2;\n --media-range-track-background: rgba(155, 155, 155, 0.5);\n --media-range-track-pointer-background: rgba(155, 155, 155, 0.5);\n --media-range-track-height: 6px;\n --media-range-track-border-radius: 4px;\n --media-box-border-radius: 4px;\n --media-time-range-buffered-color: rgba(255, 255, 255, 0.4);\n --media-range-bar-color: var(--media-accent-color);\n\n --media-range-thumb-border-radius: 13px;\n --media-range-thumb-background: var(--media-accent-color);\n --media-range-thumb-transition: transform 0.1s linear;\n --media-range-thumb-transform: scale(0) translate(0%, 0%);\n\n --media-preview-thumbnail-border: 2px solid transparent;\n --media-preview-thumbnail-border-radius: 4px;\n --media-preview-chapter-text-shadow: none;\n --media-preview-time-text-shadow: none;\n --media-preview-thumbnail-background: var(--hs-secondary-color);\n --media-preview-chapter-background: var(--hs-secondary-color);\n --media-preview-time-background: var(--hs-secondary-color);\n}\nmedia-time-range:hover {\n --media-range-thumb-transform: scale(1) translate(0%, 0%);\n}\n[mediaisfullscreen] media-time-range:hover {\n --media-range-track-height: 8px;\n}\n\nmedia-preview-thumbnail {\n background-color: var(--hs-secondary-color);\n}\n\n/* Control Bar */\nmedia-control-bar {\n position: relative;\n height: 30px;\n line-height: 30px;\n top: -2px;\n}\n[breakpointmd] media-control-bar {\n height: 40px;\n line-height: 40px;\n}\n[mediaisfullscreen] media-control-bar {\n height: 54px;\n line-height: 54px;\n}\n\n/* Play/Pause */\n/* Slow down the play icon part hiding slightly to achieve the morphing look a little better */\nmedia-play-button:not([mediapaused]) #play-p2,\nmedia-play-button:not([mediapaused]) #play-p2 {\n transition: clip-path 0.35s ease-in;\n}\n\n/* Show icon */\nmedia-play-button :is(#pause-p1, #pause-p2),\nmedia-play-button[mediapaused] :is(#play-p1, #play-p2) {\n clip-path: inset(0);\n}\n\n/* Hide icon wth clip path mask */\nmedia-play-button #play-p1 {\n clip-path: inset(0 100% 0 0);\n}\nmedia-play-button #play-p2 {\n clip-path: inset(0 20% 0 100%);\n}\nmedia-play-button[mediapaused] #pause-p1 {\n clip-path: inset(50% 0 50% 0);\n}\nmedia-play-button[mediapaused] #pause-p2 {\n clip-path: inset(50% 0 50% 0);\n}\n\n/* Volume/Mute */\nmedia-mute-button svg {\n transform: scale(0.7);\n stroke: var(--media-primary-color);\n stroke-width: 0.75px;\n}\n\nmedia-mute-button :is(#icon-muted, #icon-volume) {\n transition: clip-path 0.3s ease-out;\n}\nmedia-mute-button #icon-muted {\n clip-path: inset(0 0 100% 0);\n}\nmedia-mute-button[mediavolumelevel='off'] #icon-muted {\n clip-path: inset(0);\n}\nmedia-mute-button #icon-volume {\n clip-path: inset(0);\n}\nmedia-mute-button[mediavolumelevel='off'] #icon-volume {\n clip-path: inset(100% 0 0 0);\n}\n\nmedia-mute-button #volume-high,\nmedia-mute-button[mediavolumelevel='off'] #volume-high {\n opacity: 1;\n transition: opacity 0.3s;\n}\nmedia-mute-button[mediavolumelevel='low'] #volume-high,\nmedia-mute-button[mediavolumelevel='medium'] #volume-high {\n opacity: 0.2;\n}\n\nmedia-controller .media-volume-wrapper {\n position: relative;\n}\n\n.media-volume-range-wrapper {\n position: absolute;\n top: -4.25rem;\n left: -1.75rem;\n height: 40px;\n width: 100px;\n opacity: 0;\n transform: rotate(-90deg);\n z-index: 5;\n}\n.media-volume-range-inner {\n position: relative;\n top: 0;\n bottom: 0;\n right: 0;\n left: 12px; /* achieves proper vertical alignment of other popup menus, while ensuring you can move cursor up after hovering on mute button without dismissing */\n background-color: var(--hs-secondary-color-translucent);\n border: 1px solid var(--hs-menu-border-color);\n border-radius: 5px;\n}\n\nmedia-mute-button:hover + .media-volume-range-wrapper,\nmedia-mute-button:focus + .media-volume-range-wrapper,\nmedia-mute-button:focus-within + .media-volume-range-wrapper,\n.media-volume-range-wrapper:hover,\n.media-volume-range-wrapper:focus,\n.media-volume-range-wrapper:focus-within {\n opacity: 1;\n}\n\nmedia-volume-range {\n height: 100%;\n width: 100%;\n vertical-align: middle;\n --media-range-track-background: var(--media-primary-color);\n}\n\n/* Time Display */\nmedia-time-display {\n padding-top: 6px;\n padding-bottom: 6px;\n padding-left: 0;\n}\n[mediaisfullscreen] media-time-display {\n font-size: var(--hs-fullscreen-font-size);\n}\n\n/* Control spacer */\n.control-spacer {\n flex-grow: 1;\n}\n\n/* Subtitles/CC Button */\nmedia-captions-button {\n position: relative;\n}\n\nmedia-captions-menu-button svg {\n transform: scale(0.7);\n stroke: var(--media-primary-color);\n stroke-width: 0.5px;\n}\n\n/* Hide the captions menu button when no subtitles are available */\nmedia-captions-menu-button:not([mediasubtitleslist]) {\n display: none;\n}\n\n/* Disble the captions button when no subtitles are available */\nmedia-captions-button:not([mediasubtitleslist]) svg {\n opacity: 0.3;\n}\n\nmedia-captions-button[mediasubtitleslist]:after {\n content: '';\n display: block;\n position: absolute;\n width: 0;\n height: 3px;\n border-radius: 3px;\n background-color: var(--media-accent-color);\n bottom: 19%;\n left: 50%;\n transition: all 0.1s cubic-bezier(0, 0, 0.2, 1),\n width 0.1s cubic-bezier(0, 0, 0.2, 1);\n}\n\nmedia-captions-button[mediasubtitleslist][aria-checked='true']:after {\n left: 25%;\n width: 50%;\n transition: left 0.25s cubic-bezier(0, 0, 0.2, 1),\n width 0.25s cubic-bezier(0, 0, 0.2, 1);\n}\n\nmedia-captions-button[mediasubtitleslist][aria-checked='true']:after {\n left: 25%;\n width: 50%;\n\n transition: left 0.25s cubic-bezier(0, 0, 0.2, 1),\n width 0.25s cubic-bezier(0, 0, 0.2, 1);\n}\n\n/* Settings Menu Button */\nmedia-settings-menu-button svg {\n stroke: var(--media-primary-color);\n stroke-width: 1px;\n transition: transform 0.1s cubic-bezier(0.4, 0, 1, 1);\n transform: scale(0.55) rotateZ(0deg);\n}\nmedia-settings-menu-button[aria-expanded='true'] svg {\n stroke: var(--media-primary-color);\n stroke-width: 0.5px;\n transform: scale(0.55) rotateZ(30deg);\n}\n\n/* Fullscreen Button */\nmedia-fullscreen-button[mediaisfullscreen] svg {\n transform: scale(0.6);\n}\n";
|
|
2
|
+
export default _default;
|
|
3
|
+
//# sourceMappingURL=player-theme-styles.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"player-theme-styles.d.ts","sourceRoot":"","sources":["../../src/styles/player-theme-styles.ts"],"names":[],"mappings":"wBAAe,4tXAA4tX;AAA3uX,wBAA4uX"}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hubspot/video-player-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.21",
|
|
4
4
|
"description": "Core video player components",
|
|
5
|
-
"main": "dist/index.
|
|
5
|
+
"main": "dist/index.cjs",
|
|
6
6
|
"module": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
8
|
"license": "Apache-2.0",
|
|
@@ -14,15 +14,13 @@
|
|
|
14
14
|
".": {
|
|
15
15
|
"types": "./dist/index.d.ts",
|
|
16
16
|
"import": "./dist/index.js",
|
|
17
|
+
"require": "./dist/index.cjs",
|
|
17
18
|
"default": "./dist/index.js"
|
|
18
19
|
}
|
|
19
20
|
},
|
|
20
21
|
"files": [
|
|
21
22
|
"dist"
|
|
22
23
|
],
|
|
23
|
-
"sideEffects": [
|
|
24
|
-
"dist/styles/*.css"
|
|
25
|
-
],
|
|
26
24
|
"dependencies": {
|
|
27
25
|
"@mux/mux-video": "0.30.3",
|
|
28
26
|
"media-chrome": "^4.13.1",
|
|
@@ -34,14 +32,18 @@
|
|
|
34
32
|
"devDependencies": {
|
|
35
33
|
"@testing-library/react": "^13.4.0",
|
|
36
34
|
"@types/react": "^18.0.0",
|
|
37
|
-
"@vitejs/plugin-react": "^
|
|
35
|
+
"@vitejs/plugin-react": "^4.0.0",
|
|
38
36
|
"jsdom": "^20.0.1",
|
|
37
|
+
"vite": "5.4.8",
|
|
38
|
+
"vite-plugin-css-injected-by-js": "^3.0.0",
|
|
39
|
+
"vite-plugin-dts": "^4.5.4",
|
|
39
40
|
"vitest": "^0.24.3"
|
|
40
41
|
},
|
|
41
42
|
"scripts": {
|
|
42
|
-
"
|
|
43
|
-
"build
|
|
44
|
-
"
|
|
43
|
+
"clean": "rm -rf dist",
|
|
44
|
+
"build": "vite build",
|
|
45
|
+
"build:clean": "yarn clean && yarn build",
|
|
46
|
+
"build:watch": "vite build --watch",
|
|
45
47
|
"lint": "eslint .",
|
|
46
48
|
"test": "vitest run",
|
|
47
49
|
"flow": "flow"
|
package/dist/api/utils.js
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
function getApiDomain(env) {
|
|
2
|
-
const computedEnv = env || process.env.HS_ENV || 'prod';
|
|
3
|
-
return computedEnv === 'qa' ? 'hubspotqa.com' : 'hubspot.com';
|
|
4
|
-
}
|
|
5
|
-
export function getApiOrigin(env, hublet = 'na1') {
|
|
6
|
-
const apiSubdomainWithHublet = hublet === 'na1' ? 'api' : `api-${hublet}`;
|
|
7
|
-
return `https://${apiSubdomainWithHublet}.${getApiDomain(env)}`;
|
|
8
|
-
}
|
package/dist/api/video.js
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { getApiOrigin } from './utils';
|
|
2
|
-
export async function fetchVideo(videoId, portalId, env, hublet, skip) {
|
|
3
|
-
if (skip) {
|
|
4
|
-
return { video: undefined };
|
|
5
|
-
}
|
|
6
|
-
const response = await fetch(`${getApiOrigin(env, hublet)}/video/v1/public/${videoId}/player?portalId=${portalId}`);
|
|
7
|
-
if (!response.ok) {
|
|
8
|
-
const err = Object.assign(new Error('Failed to fetch video'), {
|
|
9
|
-
status: response.status,
|
|
10
|
-
});
|
|
11
|
-
throw err;
|
|
12
|
-
}
|
|
13
|
-
return (await response.json());
|
|
14
|
-
}
|
|
15
|
-
export async function fetchSubtitles(videoId, portalId, env, hublet) {
|
|
16
|
-
const response = await fetch(`${getApiOrigin(env, hublet)}/video/v1/public/${videoId}/subtitles?portalId=${portalId}`);
|
|
17
|
-
if (!response.ok) {
|
|
18
|
-
const err = Object.assign(new Error('Failed to fetch subtitles'), response);
|
|
19
|
-
throw err;
|
|
20
|
-
}
|
|
21
|
-
return (await response.json());
|
|
22
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useSubtitles } from '../hooks/useSubtitles';
|
|
3
|
-
const languageNames = new Intl.DisplayNames(['en'], { type: 'language' });
|
|
4
|
-
// todo first captions track is automatically enabled
|
|
5
|
-
export const CaptionTracks = ({ videoId, portalId, hsEnv, }) => {
|
|
6
|
-
const { subtitles } = useSubtitles(videoId, portalId, hsEnv);
|
|
7
|
-
return (_jsx(_Fragment, { children: subtitles &&
|
|
8
|
-
Object.entries(subtitles).map(([language, url]) => (_jsx("track", { src: url, kind: "captions", srcLang: language, label: languageNames.of(language), style: {
|
|
9
|
-
display: 'none',
|
|
10
|
-
} }, language))) }));
|
|
11
|
-
};
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
export const BigPlayIcon = () => (
|
|
3
|
-
// @ts-expect-error slot needed for media-chrome but not supported in react types
|
|
4
|
-
_jsx("svg", { slot: "play", "aria-hidden": "true", viewBox: "-1 0 23 24", children: _jsx("path", { fillRule: "evenodd", d: "M5.5 6c0-.8.8-1.3 1.5-.9l10 6c.7.4.7 1.4 0 1.8l-10 6c-.7.4-1.5-.1-1.5-.9V6Z", clipRule: "evenodd" }) }));
|
|
5
|
-
export const PlayPauseIcon = () => (_jsx("svg", { viewBox: "0 0 36 36", children: _jsxs("g", { id: "icon-play", children: [_jsxs("g", { id: "play-icon", children: [_jsx("path", { id: "play-p1", d: "M18.5 14L12 10V26L18.5 22V14Z" }), _jsx("path", { id: "play-p2", d: "M18 13.6953L25 18L18 22.3086V13.6953Z" })] }), _jsxs("g", { id: "pause-icon", children: [_jsx("path", { id: "pause-p1", d: "M16 10H12V26H16V10Z" }), _jsx("path", { id: "pause-p2", d: "M21 10H25V26H21V10Z" })] })] }) }));
|
|
6
|
-
export const VolumeHighIcon = () => (_jsxs("svg", { viewBox: "0 0 36 36", children: [_jsx("use", { className: "svg-shadow", xlinkHref: "#icon-volume" }), _jsxs("g", { id: "icon-volume", children: [_jsx("path", { d: "M19.29,29.28c-.2,0-.39-.05-.57-.15l-11.61-6.71H1c-.3,0-.59-.12-.81-.33-.21-.21-.33-.51-.33-.81v-11.43c0-.63.51-1.14,1.14-1.14l6.04-.03,11.68-6.68c.35-.2.79-.2,1.14,0,.35.2.57.58.57.99v25.14c0,.41-.22.79-.57.99-.18.1-.37.15-.57.15ZM7.41,20.14c.2,0,.4.05.57.15l10.17,5.87V4.97l-10.24,5.85c-.17.1-.36.15-.56.15l-5.2.03v9.15h5.26Z" }), _jsx("path", { d: "M6.71,17.86c-.63,0-1.14-.51-1.14-1.14v-2.29c0-.63.51-1.14,1.14-1.14s1.14.51,1.14,1.14v2.29c0,.63-.51,1.14-1.14,1.14Z" }), _jsx("path", { d: "M23.86,24.8c-.55,0-1.03-.4-1.13-.95-.11-.62.31-1.21.94-1.32,3.42-.58,5.9-3.51,5.9-6.96s-2.48-6.39-5.9-6.96c-.62-.1-1.04-.69-.94-1.32.1-.62.69-1.04,1.32-.94,4.52.76,7.81,4.64,7.81,9.22s-3.28,8.45-7.81,9.22c-.06.01-.13.02-.19.02Z" }), _jsx("path", { d: "M23.86,19.04c-.39,0-.77-.2-.98-.56-.32-.54-.14-1.24.4-1.57.51-.3.81-.8.81-1.34s-.3-1.03-.81-1.34c-.54-.32-.72-1.02-.4-1.57.32-.54,1.02-.72,1.57-.4,1.21.72,1.93,1.95,1.93,3.31s-.72,2.59-1.93,3.3c-.18.11-.38.16-.58.16Z" })] })] }));
|
|
7
|
-
export const VolumeLowIcon = () => (_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 36 36", children: [_jsx("use", { className: "svg-shadow", xlinkHref: "#icon-volume" }), _jsxs("g", { id: "icon-volume", children: [_jsx("path", { d: "M21.29,29.29c-.2,0-.39-.05-.57-.15l-11.61-6.7H3c-.3,0-.59-.12-.81-.33-.21-.21-.33-.5-.33-.81v-11.43c0-.63.51-1.14,1.14-1.14l6.04-.03,11.68-6.68c.35-.2.79-.2,1.14,0,.35.2.57.58.57.99v25.14c0,.41-.22.79-.57.99-.18.1-.37.15-.57.15ZM9.41,20.14c.2,0,.4.05.57.15l10.17,5.87V4.97l-10.24,5.85c-.17.1-.36.15-.56.15l-5.2.03v9.15h5.26Z" }), _jsx("path", { d: "M8.71,17.86c-.63,0-1.14-.51-1.14-1.14v-2.29c0-.63.51-1.14,1.14-1.14s1.14.51,1.14,1.14v2.29c0,.63-.51,1.14-1.14,1.14Z" }), _jsx("path", { d: "M25.86,20.76c-.55,0-1.03-.39-1.13-.95-.11-.62.31-1.21.94-1.32,1.43-.24,2.48-1.47,2.48-2.92s-1.04-2.68-2.48-2.92c-.62-.1-1.04-.69-.94-1.32.1-.62.69-1.04,1.32-.94,2.54.43,4.38,2.6,4.38,5.17s-1.84,4.74-4.38,5.17c-.06.01-.13.02-.19.02Z" })] })] }));
|
|
8
|
-
export const VolumeMuteIcon = () => (_jsxs("svg", { viewBox: "0 0 36 36", children: [_jsx("use", { className: "svg-shadow", xlinkHref: "#icon-muted" }), _jsxs("g", { id: "icon-muted", children: [_jsx("path", { d: "M28.07,29.29c-.29,0-.58-.11-.81-.33L2.19,3.88c-.45-.45-.45-1.17,0-1.62.45-.45,1.17-.45,1.62,0l25.07,25.07c.45.45.45,1.17,0,1.62-.22.22-.52.33-.81.33Z" }), _jsx("path", { d: "M23.5,19c-.63,0-1.14-.51-1.14-1.14V4.97l-6.72,3.84c-.55.31-1.25.12-1.56-.43-.31-.55-.12-1.25.42-1.56l8.43-4.81c.35-.2.79-.2,1.14,0,.35.2.57.58.57.99v14.86c0,.63-.51,1.14-1.14,1.14Z" }), _jsx("path", { d: "M19.29,26.85c-.19,0-.39-.05-.57-.15l-7.4-4.27h-6.1c-.3,0-.59-.12-.81-.33-.21-.21-.33-.5-.33-.81v-9.14c0-.63.51-1.14,1.14-1.14s1.14.51,1.14,1.14v8h5.26c.2,0,.4.05.57.15l7.67,4.43c.55.32.73,1.01.42,1.56-.21.37-.6.57-.99.57Z" })] })] }));
|
|
9
|
-
export const CaptionsMenuIcon = () => (_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 32 32", children: [_jsx("use", { className: "svg-shadow", xlinkHref: "#captions-menu-icon" }), _jsxs("g", { id: "captions-menu-icon", children: [_jsx("path", { d: "M28.43,27.71H3.29c-1.89,0-3.43-1.54-3.43-3.43v-16c0-1.89,1.54-3.43,3.43-3.43h25.14c1.89,0,3.43,1.54,3.43,3.43v16c0,1.89-1.54,3.43-3.43,3.43ZM3.29,7.14c-.63,0-1.14.51-1.14,1.14v16c0,.63.51,1.14,1.14,1.14h25.14c.63,0,1.14-.51,1.14-1.14v-16c0-.63-.51-1.14-1.14-1.14H3.29Z" }), _jsx("path", { d: "M5.38,16.28c0-3.58,2.67-6.2,6.31-6.2,1.12,0,2.41.33,3.29.95v2.45c-.81-.72-1.98-1.17-3.2-1.17-2.24,0-3.94,1.59-3.94,3.98s1.7,3.98,3.94,3.98c1.16,0,2.4-.45,3.2-1.17v2.45c-.88.62-2.17.95-3.29.95-3.64,0-6.31-2.62-6.31-6.2h0Z" }), _jsx("path", { d: "M16.38,16.28c0-3.58,2.67-6.2,6.31-6.2,1.12,0,2.41.33,3.29.95v2.45c-.81-.72-1.98-1.17-3.2-1.17-2.24,0-3.94,1.59-3.94,3.98s1.7,3.98,3.94,3.98c1.16,0,2.4-.45,3.2-1.17v2.45c-.88.62-2.17.95-3.29.95-3.64,0-6.31-2.62-6.31-6.2h0Z" })] })] }));
|
|
10
|
-
export const SettingsMenuIcon = () => (_jsxs("svg", { viewBox: "0 0 32 32", children: [_jsx("use", { className: "svg-shadow", xlinkHref: "#settings-icon" }), _jsxs("g", { id: "settings-icon", children: [_jsx("path", { d: "M16.26,21.57c-3.15,0-5.71-2.56-5.71-5.71,0-1.01.27-2,.77-2.86.32-.55,1.02-.73,1.56-.42.55.32.73,1.02.42,1.56-.3.52-.46,1.11-.46,1.72,0,1.89,1.54,3.43,3.43,3.43s3.43-1.54,3.43-3.43-1.54-3.43-3.43-3.43c-.63,0-1.14-.51-1.14-1.14s.51-1.14,1.14-1.14c3.15,0,5.71,2.56,5.71,5.71s-2.56,5.71-5.71,5.71Z" }), _jsx("path", { d: "M16.89,31.86h-1.27c-1.48,0-2.79-.94-3.25-2.35l-.44-1.34c-.09-.27-.27-.49-.51-.63l-2.9-1.68c-.22-.13-.49-.18-.74-.14l-1.34.2c-1.31.19-2.62-.39-3.36-1.49l-.64-.96c-.77-1.15-.77-2.65,0-3.8l1.06-1.58c.12-.19.19-.41.19-.63v-3.19c0-.23-.07-.44-.19-.63l-1.06-1.58c-.77-1.16-.77-2.65,0-3.8l.64-.95c.73-1.1,2.05-1.68,3.36-1.49l1.34.2c.25.04.52-.01.74-.14l2.94-1.71c.23-.13.4-.34.5-.59l.56-1.48c.5-1.33,1.79-2.22,3.21-2.22h1.08c1.42,0,2.71.89,3.21,2.22l.55,1.48c.09.25.27.46.5.59l2.94,1.71c.22.13.49.18.74.14l1.34-.2c1.31-.19,2.63.39,3.36,1.49l.6.9c.79,1.19.76,2.76-.08,3.91l-.99,1.36c-.12.19-.18.41-.18.63l.02,3.36c0,.22.07.44.19.63l1.06,1.59c.77,1.16.77,2.65,0,3.8l-.64.95c-.73,1.1-2.05,1.68-3.36,1.49l-1.34-.2c-.25-.04-.52.01-.74.14l-2.9,1.68c-.24.14-.42.36-.51.63l-.44,1.33c-.47,1.41-1.77,2.35-3.25,2.35ZM7.93,23.42c.6,0,1.2.16,1.73.46l2.9,1.68c.73.42,1.27,1.09,1.53,1.89l.44,1.34c.16.47.59.78,1.08.78h1.27c.49,0,.93-.32,1.08-.78l.44-1.34c.27-.8.81-1.47,1.53-1.89l2.9-1.68c.67-.39,1.46-.54,2.23-.43l1.34.2c.44.07.87-.13,1.12-.5l.64-.95c.26-.38.26-.88,0-1.27l-1.06-1.59c-.37-.56-.57-1.21-.58-1.88l-.02-3.36c0-.72.23-1.45.65-2.03l.95-1.31c.28-.38.29-.91.03-1.3l-.6-.9c-.24-.37-.68-.57-1.12-.5l-1.34.2c-.76.11-1.55-.04-2.23-.43l-2.95-1.71c-.68-.39-1.21-1.02-1.49-1.76l-.56-1.48c-.17-.44-.6-.74-1.07-.74h-1.08c-.47,0-.9.3-1.07.74l-.56,1.48c-.28.74-.81,1.37-1.49,1.76l-2.94,1.71c-.67.39-1.46.54-2.23.43l-1.34-.2c-.44-.07-.88.13-1.12.5l-.64.95c-.26.38-.26.88,0,1.27l1.06,1.58c.38.57.58,1.23.58,1.9v3.19c0,.68-.2,1.33-.58,1.9l-1.06,1.59c-.26.39-.26.88,0,1.27l.64.95c.24.37.69.57,1.12.5l1.34-.2c.17-.02.33-.04.5-.04Z" })] })] }));
|
|
11
|
-
export const PipIcon = () => (_jsxs("svg", { viewBox: "0 0 36 36", children: [_jsx("use", { className: "svg-shadow", xlinkHref: "#pip-icon" }), _jsx("path", { d: "M25 17H17V23H25V17Z" }), _jsx("path", { id: "pip-icon", d: "M7 11C7 9.89543 7.89545 9 9 9H27.0161C28.1207 9 29.0161 9.89543 29.0161 11V24.8837C29.0161 25.9883 28.1207 26.8837 27.0162 26.8837H9C7.89545 26.8837 7 25.9883 7 24.8837V11ZM9 11H27V25H9V11Z" })] }));
|
|
12
|
-
export const EnterFullscreenIcon = () => (_jsxs("svg", { viewBox: "0 0 36 36", children: [_jsx("use", { className: "svg-shadow", xlinkHref: "#fs-enter-paths" }), _jsxs("g", { id: "fs-enter-paths", children: [_jsx("path", { className: "ulbounce", d: "M11 15H9V9H15V11H11V15Z" }), _jsx("path", { className: "urbounce", d: "M21 11L21 9L27 9L27 15L25 15L25 11L21 11Z" }), _jsx("path", { className: "dlbounce", d: "M15 25L15 27L9 27L9 21L11 21L11 25L15 25Z" }), _jsx("path", { className: "drbounce", d: "M25 21L27 21L27 27L21 27L21 25L25 25L25 21Z" })] })] }));
|
|
13
|
-
export const ExitFullscreenIcon = () => (_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 32 32", children: [_jsx("use", { className: "svg-shadow", xlinkHref: "#fs-exit-paths" }), _jsxs("g", { id: "fs-exit-paths", children: [_jsx("path", { d: "M19,13.28c-.29,0-.58-.11-.81-.33-.45-.45-.45-1.17,0-1.62L27.33,2.19c.45-.45,1.17-.45,1.62,0,.45.45.45,1.17,0,1.62l-9.14,9.14c-.22.22-.52.33-.81.33Z" }), _jsx("path", { d: "M3,29.28c-.29,0-.58-.11-.81-.33-.45-.45-.45-1.17,0-1.62l9.14-9.14c.45-.45,1.17-.45,1.62,0,.45.45.45,1.17,0,1.62L3.81,28.95c-.22.22-.52.33-.81.33Z" }), _jsx("path", { d: "M28.14,13.28h-9.14c-.63,0-1.14-.51-1.14-1.14V3c0-.63.51-1.14,1.14-1.14s1.14.51,1.14,1.14v8h8c.63,0,1.14.51,1.14,1.14s-.51,1.14-1.14,1.14Z" }), _jsx("path", { d: "M12.14,29.28c-.63,0-1.14-.51-1.14-1.14v-8H3c-.63,0-1.14-.51-1.14-1.14s.51-1.14,1.14-1.14h9.14c.63,0,1.14.51,1.14,1.14v9.14c0,.63-.51,1.14-1.14,1.14Z" })] })] }));
|
|
14
|
-
export const CloseIcon = () => (_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "28", height: "28", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [_jsx("path", { d: "M18 6 6 18" }), _jsx("path", { d: "m6 6 12 12" })] }));
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { MediaControlBar, MediaController, MediaErrorDialog, MediaFullscreenButton, MediaLoadingIndicator, MediaMuteButton, MediaPipButton, MediaPlayButton, MediaPreviewChapterDisplay, MediaPreviewThumbnail, MediaPreviewTimeDisplay, MediaTimeDisplay, MediaTimeRange, MediaVolumeRange, } from 'media-chrome/react';
|
|
3
|
-
import { MediaCaptionsMenu, MediaCaptionsMenuButton, MediaPlaybackRateMenu, MediaRenditionMenu, MediaSettingsMenu, MediaSettingsMenuButton, MediaSettingsMenuItem,
|
|
4
|
-
// @ts-expect-error cannot resolve - types are incomplete
|
|
5
|
-
} from 'media-chrome/react/menu';
|
|
6
|
-
import { useEffect, useRef } from 'react';
|
|
7
|
-
import { createRoot } from 'react-dom/client';
|
|
8
|
-
import { DEFAULT_PLAY_BUTTON_COLOR } from '../constants';
|
|
9
|
-
import playerThemeStyles from '../styles/player-theme.css';
|
|
10
|
-
import { BigPlayIcon, CaptionsMenuIcon, EnterFullscreenIcon, ExitFullscreenIcon, PipIcon, PlayPauseIcon, SettingsMenuIcon, VolumeHighIcon, VolumeLowIcon, VolumeMuteIcon, } from './HSPlayerIcons';
|
|
11
|
-
import { useLanguage, useTranslations } from './TranslationsProvider';
|
|
12
|
-
import { useVideoPlayer } from './VideoPlayerProvider';
|
|
13
|
-
export const HSPlayerThemes = {
|
|
14
|
-
DARK: {
|
|
15
|
-
themeName: 'dark',
|
|
16
|
-
primaryColor: '#ffffff',
|
|
17
|
-
secondaryColor: '#4b4b4b',
|
|
18
|
-
accentColor: DEFAULT_PLAY_BUTTON_COLOR,
|
|
19
|
-
},
|
|
20
|
-
LIGHT: {
|
|
21
|
-
themeName: 'light',
|
|
22
|
-
primaryColor: '#333333',
|
|
23
|
-
secondaryColor: '#ffffff',
|
|
24
|
-
accentColor: '#460629',
|
|
25
|
-
},
|
|
26
|
-
};
|
|
27
|
-
export const HSThemeWrapper = ({ videoPlayer, themeProps, hubspotVideoParams, }) => {
|
|
28
|
-
const wrapperRef = useRef(null);
|
|
29
|
-
const containerRef = useRef(null);
|
|
30
|
-
const rootRef = useRef(null);
|
|
31
|
-
const translations = useTranslations();
|
|
32
|
-
const language = useLanguage();
|
|
33
|
-
const { actions, state } = useVideoPlayer();
|
|
34
|
-
useEffect(() => {
|
|
35
|
-
// Initialize Shadow DOM to encapsulate player styles
|
|
36
|
-
if (!wrapperRef.current?.shadowRoot) {
|
|
37
|
-
const shadowRoot = wrapperRef.current?.attachShadow({ mode: 'open' });
|
|
38
|
-
const style = document.createElement('style');
|
|
39
|
-
style.textContent = playerThemeStyles;
|
|
40
|
-
shadowRoot?.appendChild(style);
|
|
41
|
-
}
|
|
42
|
-
// Initialize React root container
|
|
43
|
-
if (!containerRef.current) {
|
|
44
|
-
containerRef.current = document.createElement('div');
|
|
45
|
-
wrapperRef.current?.shadowRoot?.appendChild(containerRef.current);
|
|
46
|
-
rootRef.current = createRoot(containerRef.current);
|
|
47
|
-
}
|
|
48
|
-
return () => {
|
|
49
|
-
if (rootRef.current) {
|
|
50
|
-
rootRef.current.unmount();
|
|
51
|
-
rootRef.current = null;
|
|
52
|
-
}
|
|
53
|
-
if (containerRef.current && wrapperRef.current?.shadowRoot) {
|
|
54
|
-
wrapperRef.current.shadowRoot.removeChild(containerRef.current);
|
|
55
|
-
containerRef.current = null;
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
}, []);
|
|
59
|
-
useEffect(() => {
|
|
60
|
-
rootRef.current?.render(_jsx(HSPlayer, { videoPlayer: videoPlayer, themeProps: themeProps, translations: translations, hubspotVideoParams: hubspotVideoParams, fullscreenElement: wrapperRef.current ?? undefined, actions: actions, language: language, isPipMode: state.isPipMode }));
|
|
61
|
-
}, [
|
|
62
|
-
videoPlayer,
|
|
63
|
-
themeProps,
|
|
64
|
-
translations,
|
|
65
|
-
hubspotVideoParams,
|
|
66
|
-
language,
|
|
67
|
-
actions,
|
|
68
|
-
state.isPipMode,
|
|
69
|
-
]);
|
|
70
|
-
return _jsx("section", { ref: wrapperRef });
|
|
71
|
-
};
|
|
72
|
-
const getIsExpanded = (element) => {
|
|
73
|
-
return element.getAttribute('aria-expanded') === 'true';
|
|
74
|
-
};
|
|
75
|
-
const HSPlayer = ({ videoPlayer, themeProps, translations, hubspotVideoParams, fullscreenElement, language, actions, isPipMode, }) => {
|
|
76
|
-
const { hide_controls } = hubspotVideoParams;
|
|
77
|
-
const captionsMenuRef = useRef(null);
|
|
78
|
-
const settingsMenuRef = useRef(null);
|
|
79
|
-
return (_jsxs(MediaController, { lang: language, breakpoints: "md:480", hotkeys: "true", defaultStreamType: "on-demand", defaultSubtitles: false, fullscreenElement: fullscreenElement, style: {
|
|
80
|
-
'--media-primary-color': themeProps.primaryColor,
|
|
81
|
-
'--media-text-color': themeProps.primaryColor,
|
|
82
|
-
'--hs-secondary-color': themeProps.secondaryColor,
|
|
83
|
-
'--hs-secondary-color-translucent': themeProps.secondaryColor + 'cc',
|
|
84
|
-
'--media-accent-color': themeProps.accentColor,
|
|
85
|
-
}, children: [videoPlayer, _jsx("slot", { name: "media", slot: "media" }), _jsx("slot", { name: "poster", slot: "poster" }), _jsx("slot", { name: "overlay", slot: "centered-chrome" }), _jsx(MediaLoadingIndicator, { noAutohide: true, slot: "centered-chrome" }), _jsx(MediaPlayButton, { slot: "centered-chrome", className: "centered-play-button", children: _jsx(BigPlayIcon, {}) }), _jsx(MediaErrorDialog, { slot: "dialog" }), _jsxs("div", { className: "hs-control-bar-background", style: { display: hide_controls ? 'none' : 'flex' }, children: [_jsxs(MediaTimeRange, { children: [_jsx(MediaPreviewThumbnail, { slot: "preview" }), _jsx(MediaPreviewChapterDisplay, { slot: "preview" }), _jsx(MediaPreviewTimeDisplay, { slot: "preview" })] }), _jsx("div", { className: "hs-bottom-gradient", "data-theme-variant": themeProps.themeName }), _jsxs(MediaControlBar, { children: [_jsx(MediaPlayButton, { className: "hs-button", mediaPaused: true, children: _jsx("span", { slot: "icon", children: _jsx(PlayPauseIcon, {}) }) }), _jsx(MediaTimeDisplay, { showDuration: true }), _jsx("span", { className: "control-spacer" }), _jsxs("div", { className: "media-volume-wrapper", children: [_jsxs(MediaMuteButton, { className: "hs-button", tooltipPlacement: "bottom", children: [_jsx("span", { slot: "high", children: _jsx(VolumeHighIcon, {}) }), _jsx("span", { slot: "medium", children: _jsx(VolumeHighIcon, {}) }), _jsx("span", { slot: "low", children: _jsx(VolumeLowIcon, {}) }), _jsx("span", { slot: "off", children: _jsx(VolumeMuteIcon, {}) })] }), _jsx("div", { className: "media-volume-range-wrapper", children: _jsx("div", { className: "media-volume-range-inner", children: _jsx(MediaVolumeRange, {}) }) })] }), _jsx(MediaCaptionsMenu, { anchor: "auto", hidden: true }), _jsx(MediaCaptionsMenuButton, { ref: captionsMenuRef, className: "hs-button", onClick: () => {
|
|
86
|
-
if (captionsMenuRef.current) {
|
|
87
|
-
actions.toggleMenu('captions', getIsExpanded(captionsMenuRef.current));
|
|
88
|
-
}
|
|
89
|
-
}, children: _jsx("span", { slot: "icon", children: _jsx(CaptionsMenuIcon, {}) }) }), _jsx(MediaSettingsMenuButton, { ref: settingsMenuRef, className: "hs-button", onClick: () => {
|
|
90
|
-
if (settingsMenuRef.current) {
|
|
91
|
-
actions.toggleMenu('settings', getIsExpanded(settingsMenuRef.current));
|
|
92
|
-
}
|
|
93
|
-
}, children: _jsx("span", { slot: "icon", children: _jsx(SettingsMenuIcon, {}) }) }), _jsxs(MediaSettingsMenu, { role: "menu", anchor: "auto", hidden: true, children: [_jsxs(MediaSettingsMenuItem, { children: [translations.playbackSpeed, _jsx(MediaPlaybackRateMenu, { slot: "submenu", hidden: true, children: _jsx("div", { slot: "title", children: translations.playbackSpeed }) })] }), _jsxs(MediaSettingsMenuItem, { children: [translations.quality, _jsx(MediaRenditionMenu, { slot: "submenu", hidden: true, children: _jsx("div", { slot: "title", children: translations.quality }) })] })] }), _jsx(MediaPipButton, { className: "hs-button", mediaIsPip: isPipMode, children: _jsx("span", { slot: "icon", children: _jsx(PipIcon, {}) }) }), _jsxs(MediaFullscreenButton, { className: "hs-button", children: [_jsx("span", { slot: "enter", children: _jsx(EnterFullscreenIcon, {}) }), _jsx("span", { slot: "exit", children: _jsx(ExitFullscreenIcon, {}) })] })] })] })] }));
|
|
94
|
-
};
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { createContext, useContext, useEffect, useMemo, } from 'react';
|
|
3
|
-
// @ts-expect-error cannot resolve
|
|
4
|
-
import { addTranslation } from 'media-chrome/utils/i18n';
|
|
5
|
-
import { createTranslationsMap, DEFAULT_LANGUAGE, DEFAULT_TRANSLATIONS, } from '../utils/i18n';
|
|
6
|
-
export const TranslationsContext = createContext({ translations: DEFAULT_TRANSLATIONS, language: DEFAULT_LANGUAGE });
|
|
7
|
-
export const TranslationsProvider = ({ children, translations, language = DEFAULT_LANGUAGE, }) => {
|
|
8
|
-
useEffect(() => {
|
|
9
|
-
if (language !== DEFAULT_LANGUAGE) {
|
|
10
|
-
// Add custom language to media-chrome's translations dictionary
|
|
11
|
-
addTranslation(language, createTranslationsMap(translations));
|
|
12
|
-
}
|
|
13
|
-
}, [language, translations]);
|
|
14
|
-
const contextValue = useMemo(() => ({
|
|
15
|
-
// provide default EN translation fallbacks for any missing keys
|
|
16
|
-
translations: { ...DEFAULT_TRANSLATIONS, ...translations },
|
|
17
|
-
language,
|
|
18
|
-
}), [translations, language]);
|
|
19
|
-
return (_jsx(TranslationsContext.Provider, { value: contextValue, children: children }));
|
|
20
|
-
};
|
|
21
|
-
export const useTranslations = () => {
|
|
22
|
-
const { translations } = useContext(TranslationsContext);
|
|
23
|
-
return translations;
|
|
24
|
-
};
|
|
25
|
-
export const useLanguage = () => {
|
|
26
|
-
const { language } = useContext(TranslationsContext);
|
|
27
|
-
return language;
|
|
28
|
-
};
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { createContext } from 'react';
|
|
3
|
-
export const VideoFetchContext = createContext({
|
|
4
|
-
video: undefined,
|
|
5
|
-
videoFetchError: undefined,
|
|
6
|
-
hubspotVideoParams: undefined,
|
|
7
|
-
hublet: 'na1',
|
|
8
|
-
env: 'prod',
|
|
9
|
-
});
|
|
10
|
-
export const VideoFetchProvider = ({ children, ...props }) => {
|
|
11
|
-
return (_jsx(VideoFetchContext.Provider, { value: props, children: children }));
|
|
12
|
-
};
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useCallback, useContext } from 'react';
|
|
3
|
-
import MuxVideo from '@mux/mux-video/react';
|
|
4
|
-
// @ts-expect-error cannot resolve - types are incomplete
|
|
5
|
-
import { useMediaRef } from 'media-chrome/react/media-store';
|
|
6
|
-
import '../styles/mux-player.css';
|
|
7
|
-
import { useVideoPlayer } from './VideoPlayerProvider';
|
|
8
|
-
import { CaptionTracks } from './CaptionTracks';
|
|
9
|
-
import { DEFAULT_HLS_OPTIONS } from '../constants';
|
|
10
|
-
import { HSPlayerThemes, HSThemeWrapper } from './HSThemeTemplate';
|
|
11
|
-
import { VideoFetchContext } from './VideoFetchProvider';
|
|
12
|
-
export const VideoPlayer = ({ portalId, playButtonColor, theme, overlay, }) => {
|
|
13
|
-
const { video, hubspotVideoParams, env } = useContext(VideoFetchContext);
|
|
14
|
-
const mediaRef = useMediaRef();
|
|
15
|
-
const themeProps = HSPlayerThemes[theme] || HSPlayerThemes.DARK;
|
|
16
|
-
const { actions, eventCallbacks } = useVideoPlayer();
|
|
17
|
-
const setMediaRef = useCallback((el) => {
|
|
18
|
-
mediaRef(el);
|
|
19
|
-
actions.setElement(el);
|
|
20
|
-
}, [mediaRef, actions.setElement]);
|
|
21
|
-
const setupChapters = async (event) => {
|
|
22
|
-
const element = event.target;
|
|
23
|
-
if (element && video?.chapters) {
|
|
24
|
-
const chapters = video.chapters.items.map((chapter, i) => ({
|
|
25
|
-
startTime: chapter.startTime,
|
|
26
|
-
endTime: video.chapters.items[i + 1]?.startTime || video.duration / 1000,
|
|
27
|
-
value: chapter.title,
|
|
28
|
-
}));
|
|
29
|
-
try {
|
|
30
|
-
await element.addChapters(chapters);
|
|
31
|
-
}
|
|
32
|
-
catch (error) {
|
|
33
|
-
console.error('addChapters failed with error:', error);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
|
-
if (!video) {
|
|
38
|
-
return null;
|
|
39
|
-
}
|
|
40
|
-
return (_jsxs("div", { style: {
|
|
41
|
-
position: 'relative',
|
|
42
|
-
maxWidth: video.width,
|
|
43
|
-
aspectRatio: `${video.width} / ${video.height}`,
|
|
44
|
-
lineHeight: 0,
|
|
45
|
-
}, children: [_jsx(HSThemeWrapper, { videoPlayer: _jsxs(MuxVideo, { ref: setMediaRef, slot: "media", crossOrigin: "anonymous", playbackId: video.primaryPlaybackId || undefined, poster: video.posterUrl, src: video.primaryPlaybackId ? undefined : video.fileUrl, playsInline: true, _hlsConfig: DEFAULT_HLS_OPTIONS, style: {
|
|
46
|
-
aspectRatio: `${video.width} / ${video.height}`,
|
|
47
|
-
}, autoplay: hubspotVideoParams?.autoplay, loop: hubspotVideoParams?.loop_video, muted: hubspotVideoParams?.mute_by_default || false, onLoadedMetadata: setupChapters, ...eventCallbacks, children: [_jsx("track", { label: "thumbnails", kind: "metadata", src: `https://image.mux.com/${video.primaryPlaybackId}/storyboard.vtt?format=webp`, default: true }), _jsx(CaptionTracks, { videoId: video.videoId, portalId: portalId, hsEnv: env })] }), hubspotVideoParams: hubspotVideoParams, themeProps: {
|
|
48
|
-
...themeProps,
|
|
49
|
-
accentColor: playButtonColor?.color || themeProps?.accentColor,
|
|
50
|
-
} }), overlay] }));
|
|
51
|
-
};
|
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { MediaActionTypes, MediaProvider, useMediaDispatch, useMediaSelector,
|
|
3
|
-
// @ts-expect-error cannot resolve - types are incomplete
|
|
4
|
-
} from 'media-chrome/react/media-store';
|
|
5
|
-
import { createContext, useContext, useEffect, useMemo, useRef, useState, } from 'react';
|
|
6
|
-
const createPlayerRegistry = () => {
|
|
7
|
-
const playersById = new Map();
|
|
8
|
-
const register = (videoId, handle) => {
|
|
9
|
-
playersById.set(videoId, handle);
|
|
10
|
-
};
|
|
11
|
-
const unregister = (videoId) => {
|
|
12
|
-
playersById.delete(videoId);
|
|
13
|
-
};
|
|
14
|
-
const activate = (videoId) => {
|
|
15
|
-
playersById.forEach((handle, id) => {
|
|
16
|
-
if (id !== videoId) {
|
|
17
|
-
handle.actions.pause('other-player');
|
|
18
|
-
}
|
|
19
|
-
});
|
|
20
|
-
};
|
|
21
|
-
return {
|
|
22
|
-
register,
|
|
23
|
-
unregister,
|
|
24
|
-
activate,
|
|
25
|
-
};
|
|
26
|
-
};
|
|
27
|
-
let videoPlayerRegistry;
|
|
28
|
-
const getVideoPlayerRegistry = () => {
|
|
29
|
-
if (!videoPlayerRegistry) {
|
|
30
|
-
videoPlayerRegistry = createPlayerRegistry();
|
|
31
|
-
}
|
|
32
|
-
return videoPlayerRegistry;
|
|
33
|
-
};
|
|
34
|
-
const VideoPlayerContext = createContext(null);
|
|
35
|
-
function addListener(el, events, fn) {
|
|
36
|
-
const listeners = events.split(' ');
|
|
37
|
-
listeners.forEach((e) => el.addEventListener(e, fn));
|
|
38
|
-
return () => {
|
|
39
|
-
listeners.forEach((e) => el.removeEventListener(e, fn));
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
const VideoPlayerInternal = ({ video, children, playerCallbacks, }) => {
|
|
43
|
-
const mediaDispatch = useMediaDispatch();
|
|
44
|
-
const [mediaElement, setMediaElement] = useState(null);
|
|
45
|
-
const [playbackStartedAt, setPlaybackStartedAt] = useState();
|
|
46
|
-
// Maintaining our own picture-in-picture state instead of using MediaState's mediaIsPip.
|
|
47
|
-
// we render MediaController in a shadow DOM, making it unable to detect pip state changes
|
|
48
|
-
const [isPipMode, setIsPipMode] = useState(false);
|
|
49
|
-
const { onPlay, onPause, onCaptionChange, onQualityChange, onPlaybackRateChange, onToggleMenu, ...callbacks } = playerCallbacks || {};
|
|
50
|
-
const prevPlaybackRateRef = useRef(1);
|
|
51
|
-
const prevRenditionRef = useRef();
|
|
52
|
-
const pauseReasonRef = useRef();
|
|
53
|
-
const state = {
|
|
54
|
-
hasStarted: useMediaSelector((state) => state.mediaHasPlayed ?? false),
|
|
55
|
-
isPaused: useMediaSelector((state) => state.mediaPaused ?? true),
|
|
56
|
-
currentTime: useMediaSelector((state) => state.mediaCurrentTime ?? 0),
|
|
57
|
-
hasEnded: useMediaSelector((state) => state.mediaEnded ?? false),
|
|
58
|
-
loadedDuration: useMediaSelector((state) => isNaN(state.mediaDuration) ? 0 : state.mediaDuration),
|
|
59
|
-
selectedCaptionTrack: useMediaSelector((state) => state.mediaSubtitlesShowing?.length
|
|
60
|
-
? state.mediaSubtitlesShowing[0].language
|
|
61
|
-
: null),
|
|
62
|
-
playbackRate: useMediaSelector((state) => state.mediaPlaybackRate ?? 1),
|
|
63
|
-
selectedRendition: useMediaSelector((state) => state.mediaRenditionSelected),
|
|
64
|
-
element: mediaElement,
|
|
65
|
-
playbackStartedAt,
|
|
66
|
-
isPipMode,
|
|
67
|
-
};
|
|
68
|
-
useEffect(() => {
|
|
69
|
-
if (onQualityChange &&
|
|
70
|
-
state.selectedRendition &&
|
|
71
|
-
prevRenditionRef.current !== state.selectedRendition) {
|
|
72
|
-
onQualityChange(state.selectedRendition);
|
|
73
|
-
}
|
|
74
|
-
prevRenditionRef.current = state.selectedRendition;
|
|
75
|
-
}, [onQualityChange, state.selectedRendition]);
|
|
76
|
-
useEffect(() => {
|
|
77
|
-
if (onCaptionChange && state.selectedCaptionTrack) {
|
|
78
|
-
onCaptionChange(state.selectedCaptionTrack);
|
|
79
|
-
}
|
|
80
|
-
}, [state.selectedCaptionTrack, onCaptionChange]);
|
|
81
|
-
useEffect(() => {
|
|
82
|
-
if (onPlaybackRateChange &&
|
|
83
|
-
prevPlaybackRateRef.current !== state.playbackRate) {
|
|
84
|
-
// Trigger callback only when playback rate changes from initial value
|
|
85
|
-
onPlaybackRateChange(state.playbackRate);
|
|
86
|
-
}
|
|
87
|
-
prevPlaybackRateRef.current = state.playbackRate;
|
|
88
|
-
}, [state.playbackRate, onPlaybackRateChange]);
|
|
89
|
-
const actions = useMemo(() => ({
|
|
90
|
-
play: () => mediaDispatch({ type: MediaActionTypes.MEDIA_PLAY_REQUEST }),
|
|
91
|
-
pause: (reason) => {
|
|
92
|
-
if (reason) {
|
|
93
|
-
// Store reason to trigger with onPause callback
|
|
94
|
-
pauseReasonRef.current = reason;
|
|
95
|
-
}
|
|
96
|
-
mediaDispatch({ type: MediaActionTypes.MEDIA_PAUSE_REQUEST });
|
|
97
|
-
},
|
|
98
|
-
toggleMenu: (menuType, isOpen) => {
|
|
99
|
-
onToggleMenu?.(menuType, isOpen);
|
|
100
|
-
},
|
|
101
|
-
setElement: setMediaElement,
|
|
102
|
-
}), [onToggleMenu]);
|
|
103
|
-
// Set up event listeners on native media element
|
|
104
|
-
useEffect(() => {
|
|
105
|
-
if (!mediaElement) {
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
const listeners = [
|
|
109
|
-
addListener(mediaElement, 'enterpictureinpicture leavepictureinpicture', (event) => setIsPipMode(event.type === 'enterpictureinpicture')),
|
|
110
|
-
];
|
|
111
|
-
return () => listeners.forEach((remove) => remove());
|
|
112
|
-
}, [mediaElement]);
|
|
113
|
-
// Register player instance
|
|
114
|
-
useEffect(() => {
|
|
115
|
-
if (!video) {
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
const registry = getVideoPlayerRegistry();
|
|
119
|
-
registry.register(video.videoId, { actions });
|
|
120
|
-
const handleVisibilityChange = () => {
|
|
121
|
-
if (document.visibilityState === 'hidden') {
|
|
122
|
-
actions.pause('visibility-hidden');
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
document.addEventListener('visibilitychange', handleVisibilityChange);
|
|
126
|
-
return () => {
|
|
127
|
-
registry.unregister(video.videoId);
|
|
128
|
-
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
|
129
|
-
};
|
|
130
|
-
}, [video]);
|
|
131
|
-
return (_jsx(VideoPlayerContext.Provider, { value: {
|
|
132
|
-
state,
|
|
133
|
-
actions,
|
|
134
|
-
eventCallbacks: {
|
|
135
|
-
...callbacks,
|
|
136
|
-
onPlay: (event) => {
|
|
137
|
-
if (video) {
|
|
138
|
-
const registry = getVideoPlayerRegistry();
|
|
139
|
-
registry.activate(video.videoId);
|
|
140
|
-
if (!playbackStartedAt) {
|
|
141
|
-
setPlaybackStartedAt(Date.now());
|
|
142
|
-
}
|
|
143
|
-
onPlay?.(event);
|
|
144
|
-
}
|
|
145
|
-
},
|
|
146
|
-
onPause: (event) => {
|
|
147
|
-
onPause?.(event, pauseReasonRef.current);
|
|
148
|
-
pauseReasonRef.current = undefined;
|
|
149
|
-
},
|
|
150
|
-
},
|
|
151
|
-
}, children: children }));
|
|
152
|
-
};
|
|
153
|
-
export const VideoPlayerProvider = ({ video, children, playerCallbacks, }) => {
|
|
154
|
-
return (_jsx(MediaProvider, { children: _jsx(VideoPlayerInternal, { video: video, playerCallbacks: playerCallbacks, children: children }) }));
|
|
155
|
-
};
|
|
156
|
-
export const useVideoPlayer = () => {
|
|
157
|
-
const context = useContext(VideoPlayerContext);
|
|
158
|
-
if (!context) {
|
|
159
|
-
throw new Error('useVideoPlayer must be used within a VideoPlayerProvider');
|
|
160
|
-
}
|
|
161
|
-
return context;
|
|
162
|
-
};
|
package/dist/constants.js
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
export const DEFAULT_ASPECT_RATIO = 9 / 16;
|
|
2
|
-
export const DEFAULT_PLAY_BUTTON_COLOR = '#F7761F';
|
|
3
|
-
// do we want `autoStartLoad: false`? this was an important cost saving measure https://git.hubteam.com/HubSpot/hubspot-video-ui/commit/dd6d03586bd3a0c6f45178f660967ca52a4898d0#diff-270ff8e18fd756a342307e5269a906b4207090351052e5b1987171753bea6386R45
|
|
4
|
-
export const DEFAULT_HLS_OPTIONS = {
|
|
5
|
-
debug: true,
|
|
6
|
-
maxBufferSize: 20,
|
|
7
|
-
maxBufferLength: 15,
|
|
8
|
-
};
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { useEffect, useMemo, useRef } from 'react';
|
|
2
|
-
/**
|
|
3
|
-
* In some cases it may be necessary to mock dismissErrorWithoutHandling
|
|
4
|
-
* in tests.
|
|
5
|
-
*/
|
|
6
|
-
export function dismissErrorWithoutHandling(error) {
|
|
7
|
-
setTimeout(() => {
|
|
8
|
-
throw error;
|
|
9
|
-
}, 0);
|
|
10
|
-
}
|
|
11
|
-
function createRethrowAsync() {
|
|
12
|
-
const syncStackError = new Error(`Encountered unexpected rejection in async side effect`);
|
|
13
|
-
return (error) => {
|
|
14
|
-
dismissErrorWithoutHandling(error);
|
|
15
|
-
dismissErrorWithoutHandling(syncStackError);
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
async function handleEffectResult(effectResult) {
|
|
19
|
-
if (effectResult instanceof Promise) {
|
|
20
|
-
return handleEffectResult(await effectResult);
|
|
21
|
-
}
|
|
22
|
-
else if (typeof effectResult === 'function') {
|
|
23
|
-
return handleEffectResult(effectResult());
|
|
24
|
-
}
|
|
25
|
-
return undefined;
|
|
26
|
-
}
|
|
27
|
-
export function useAsyncEffect(effect, deps) {
|
|
28
|
-
const rethrowAsync = useMemo(createRethrowAsync, []);
|
|
29
|
-
const { current: asyncEffectState } = useRef({
|
|
30
|
-
mostRecentEffect: effect,
|
|
31
|
-
asyncQueue: Promise.resolve(undefined),
|
|
32
|
-
done: false,
|
|
33
|
-
});
|
|
34
|
-
asyncEffectState.mostRecentEffect = effect;
|
|
35
|
-
useEffect(() => {
|
|
36
|
-
const newLink = handleEffectResult(asyncEffectState.asyncQueue)
|
|
37
|
-
.catch(rethrowAsync)
|
|
38
|
-
.then(() => {
|
|
39
|
-
// we've reached the end of the queue, so it's time to trigger work
|
|
40
|
-
if (!asyncEffectState.done && asyncEffectState.asyncQueue === newLink) {
|
|
41
|
-
return asyncEffectState.mostRecentEffect();
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
return undefined;
|
|
45
|
-
}
|
|
46
|
-
})
|
|
47
|
-
.catch(rethrowAsync);
|
|
48
|
-
asyncEffectState.asyncQueue = newLink;
|
|
49
|
-
}, [asyncEffectState, rethrowAsync, ...deps]);
|
|
50
|
-
useEffect(() => () => {
|
|
51
|
-
asyncEffectState.done = true;
|
|
52
|
-
handleEffectResult(asyncEffectState.asyncQueue).catch(rethrowAsync);
|
|
53
|
-
}, [asyncEffectState, rethrowAsync]);
|
|
54
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from 'react';
|
|
2
|
-
import { fetchSubtitles } from '../api/video';
|
|
3
|
-
export const useSubtitles = (videoId, portalId, env, hublet) => {
|
|
4
|
-
const [fetchError, setFetchError] = useState();
|
|
5
|
-
const [subtitles, setSubtitles] = useState();
|
|
6
|
-
useEffect(() => {
|
|
7
|
-
fetchSubtitles(videoId, portalId, env, hublet)
|
|
8
|
-
.then((resp) => {
|
|
9
|
-
setSubtitles(resp);
|
|
10
|
-
})
|
|
11
|
-
.catch((err) => {
|
|
12
|
-
console.error('Subtitles fetch failed:', err);
|
|
13
|
-
setFetchError(err);
|
|
14
|
-
});
|
|
15
|
-
}, [videoId, portalId, env, hublet]);
|
|
16
|
-
return { subtitles, fetchError };
|
|
17
|
-
};
|
package/dist/hooks/useVideo.js
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
|
-
import { fetchVideo } from '../api/video';
|
|
3
|
-
import { useAsyncEffect } from './useAsyncEffect';
|
|
4
|
-
export const useVideo = (videoId, portalId, env, hublet, skip) => {
|
|
5
|
-
const [fetchError, setFetchError] = useState();
|
|
6
|
-
const [video, setVideo] = useState();
|
|
7
|
-
useAsyncEffect(async () => {
|
|
8
|
-
try {
|
|
9
|
-
const resp = await fetchVideo(videoId, portalId, env, hublet, skip);
|
|
10
|
-
setVideo(resp.video);
|
|
11
|
-
}
|
|
12
|
-
catch (err) {
|
|
13
|
-
setFetchError(err);
|
|
14
|
-
}
|
|
15
|
-
}, [videoId, env, hublet, skip]);
|
|
16
|
-
return { video, fetchError };
|
|
17
|
-
};
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
@font-face {
|
|
2
|
-
font-family: Lexend Deca;
|
|
3
|
-
src: url(//static.hsappstatic.net/ui-fonts/static-1.291/fonts/LexendDeca-Light.woff2)
|
|
4
|
-
format('woff2');
|
|
5
|
-
}
|
|
6
|
-
@font-face {
|
|
7
|
-
font-family: Lexend Deca;
|
|
8
|
-
src: url(//static.hsappstatic.net/ui-fonts/static-1.291/fonts/LexendDeca-Medium.woff2)
|
|
9
|
-
format('woff2');
|
|
10
|
-
font-weight: 500;
|
|
11
|
-
}
|
|
12
|
-
@font-face {
|
|
13
|
-
font-family: Lexend Deca;
|
|
14
|
-
src: url(//static.hsappstatic.net/ui-fonts/static-1.291/fonts/LexendDeca-SemiBold.woff2)
|
|
15
|
-
format('woff2');
|
|
16
|
-
font-weight: 600;
|
|
17
|
-
}
|
|
18
|
-
@font-face {
|
|
19
|
-
font-family: Lexend Deca;
|
|
20
|
-
src: url(//static.hsappstatic.net/ui-fonts/static-1.291/fonts/LexendDeca-Bold.woff2)
|
|
21
|
-
format('woff2');
|
|
22
|
-
font-weight: 700;
|
|
23
|
-
}
|