UNPKG

@mottosports/motto-video-player

Version:

React video player component for the Motto platform, powered by Shaka Player

1,634 lines (1,624 loc) 199 kB
"use client"; // src/index.ts import "shaka-player/dist/controls.css"; // #style-inject:#style-inject function styleInject(css, { insertAt } = {}) { if (!css || typeof document === "undefined") return; const head = document.head || document.getElementsByTagName("head")[0]; const style = document.createElement("style"); style.type = "text/css"; if (insertAt === "top") { if (head.firstChild) { head.insertBefore(style, head.firstChild); } else { head.appendChild(style); } } else { head.appendChild(style); } if (style.styleSheet) { style.styleSheet.cssText = css; } else { style.appendChild(document.createTextNode(css)); } } // src/index.css styleInject(`/*! tailwindcss v4.1.8 | MIT License | https://tailwindcss.com */ @layer properties; @layer theme, base, components, utilities; @layer theme { :root, :host { --font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; --color-red-600: oklch(57.7% 0.245 27.325); --color-black: #000; --color-white: #fff; --spacing: 0.25rem; --text-xs: 0.75rem; --text-xs--line-height: calc(1 / 0.75); --text-sm: 0.875rem; --text-sm--line-height: calc(1.25 / 0.875); --text-base: 1rem; --text-base--line-height: calc(1.5 / 1); --text-lg: 1.125rem; --text-lg--line-height: calc(1.75 / 1.125); --text-xl: 1.25rem; --text-xl--line-height: calc(1.75 / 1.25); --text-2xl: 1.5rem; --text-2xl--line-height: calc(2 / 1.5); --text-5xl: 3rem; --text-5xl--line-height: 1; --font-weight-medium: 500; --font-weight-semibold: 600; --font-weight-bold: 700; --tracking-wide: 0.025em; --tracking-widest: 0.1em; --radius-md: 0.375rem; --radius-2xl: 1rem; --animate-spin: spin 1s linear infinite; --animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; --aspect-video: 16 / 9; --default-transition-duration: 150ms; --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); --default-font-family: var(--font-sans); --default-mono-font-family: var(--font-mono); } } @layer base { *, ::after, ::before, ::backdrop, ::file-selector-button { box-sizing: border-box; margin: 0; padding: 0; border: 0 solid; } html, :host { line-height: 1.5; -webkit-text-size-adjust: 100%; tab-size: 4; font-family: var(--default-font-family, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"); font-feature-settings: var(--default-font-feature-settings, normal); font-variation-settings: var(--default-font-variation-settings, normal); -webkit-tap-highlight-color: transparent; } hr { height: 0; color: inherit; border-top-width: 1px; } abbr:where([title]) { -webkit-text-decoration: underline dotted; text-decoration: underline dotted; } h1, h2, h3, h4, h5, h6 { font-size: inherit; font-weight: inherit; } a { color: inherit; -webkit-text-decoration: inherit; text-decoration: inherit; } b, strong { font-weight: bolder; } code, kbd, samp, pre { font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace); font-feature-settings: var(--default-mono-font-feature-settings, normal); font-variation-settings: var(--default-mono-font-variation-settings, normal); font-size: 1em; } small { font-size: 80%; } sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; } sub { bottom: -0.25em; } sup { top: -0.5em; } table { text-indent: 0; border-color: inherit; border-collapse: collapse; } :-moz-focusring { outline: auto; } progress { vertical-align: baseline; } summary { display: list-item; } ol, ul, menu { list-style: none; } img, svg, video, canvas, audio, iframe, embed, object { display: block; vertical-align: middle; } img, video { max-width: 100%; height: auto; } button, input, select, optgroup, textarea, ::file-selector-button { font: inherit; font-feature-settings: inherit; font-variation-settings: inherit; letter-spacing: inherit; color: inherit; border-radius: 0; background-color: transparent; opacity: 1; } :where(select:is([multiple], [size])) optgroup { font-weight: bolder; } :where(select:is([multiple], [size])) optgroup option { padding-inline-start: 20px; } ::file-selector-button { margin-inline-end: 4px; } ::placeholder { opacity: 1; } @supports (not (-webkit-appearance: -apple-pay-button)) or (contain-intrinsic-size: 1px) { ::placeholder { color: currentcolor; @supports (color: color-mix(in lab, red, red)) { color: color-mix(in oklab, currentcolor 50%, transparent); } } } textarea { resize: vertical; } ::-webkit-search-decoration { -webkit-appearance: none; } ::-webkit-date-and-time-value { min-height: 1lh; text-align: inherit; } ::-webkit-datetime-edit { display: inline-flex; } ::-webkit-datetime-edit-fields-wrapper { padding: 0; } ::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field { padding-block: 0; } :-moz-ui-invalid { box-shadow: none; } button, input:where([type=button], [type=reset], [type=submit]), ::file-selector-button { appearance: button; } ::-webkit-inner-spin-button, ::-webkit-outer-spin-button { height: auto; } [hidden]:where(:not([hidden=until-found])) { display: none !important; } } @layer utilities { .pointer-events-none { pointer-events: none; } .visible { visibility: visible; } .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border-width: 0; } .absolute { position: absolute; } .fixed { position: fixed; } .relative { position: relative; } .static { position: static; } .inset-0 { inset: calc(var(--spacing) * 0); } .top-4 { top: calc(var(--spacing) * 4); } .right-0 { right: calc(var(--spacing) * 0); } .right-4 { right: calc(var(--spacing) * 4); } .bottom-0 { bottom: calc(var(--spacing) * 0); } .bottom-4 { bottom: calc(var(--spacing) * 4); } .left-0 { left: calc(var(--spacing) * 0); } .left-4 { left: calc(var(--spacing) * 4); } .z-10 { z-index: 10; } .z-20 { z-index: 20; } .z-50 { z-index: 50; } .container { width: 100%; @media (width >= 40rem) { max-width: 40rem; } @media (width >= 48rem) { max-width: 48rem; } @media (width >= 64rem) { max-width: 64rem; } @media (width >= 80rem) { max-width: 80rem; } @media (width >= 96rem) { max-width: 96rem; } } .m-6 { margin: calc(var(--spacing) * 6); } .mt-1 { margin-top: calc(var(--spacing) * 1); } .mt-3 { margin-top: calc(var(--spacing) * 3); } .mb-2 { margin-bottom: calc(var(--spacing) * 2); } .mb-6 { margin-bottom: calc(var(--spacing) * 6); } .flex { display: flex; } .grid { display: grid; } .aspect-video { aspect-ratio: var(--aspect-video); } .h-2 { height: calc(var(--spacing) * 2); } .h-12 { height: calc(var(--spacing) * 12); } .h-24 { height: calc(var(--spacing) * 24); } .h-full { height: 100%; } .w-2 { width: calc(var(--spacing) * 2); } .w-12 { width: calc(var(--spacing) * 12); } .w-24 { width: calc(var(--spacing) * 24); } .w-full { width: 100%; } .grow { flex-grow: 1; } .animate-pulse { animation: var(--animate-pulse); } .animate-spin { animation: var(--animate-spin); } .auto-cols-max { grid-auto-columns: max-content; } .grid-flow-col { grid-auto-flow: column; } .flex-col { flex-direction: column; } .items-center { align-items: center; } .justify-center { justify-content: center; } .justify-stretch { justify-content: stretch; } .gap-1 { gap: calc(var(--spacing) * 1); } .overflow-hidden { overflow: hidden; } .rounded-full { border-radius: calc(infinity * 1px); } .rounded-md { border-radius: var(--radius-md); } .bg-\\[\\#111111\\] { background-color: #111111; } .bg-\\[\\#151515\\] { background-color: #151515; } .bg-black { background-color: var(--color-black); } .bg-red-600 { background-color: var(--color-red-600); } .bg-transparent { background-color: transparent; } .bg-white { background-color: var(--color-white); } .bg-gradient-to-t { --tw-gradient-position: to top in oklab; background-image: linear-gradient(var(--tw-gradient-stops)); } .from-black\\/70 { --tw-gradient-from: color-mix(in srgb, #000 70%, transparent); @supports (color: color-mix(in lab, red, red)) { --tw-gradient-from: color-mix(in oklab, var(--color-black) 70%, transparent); } --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); } .to-transparent { --tw-gradient-to: transparent; --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); } .bg-cover { background-size: cover; } .bg-center { background-position: center; } .bg-no-repeat { background-repeat: no-repeat; } .p-2 { padding: calc(var(--spacing) * 2); } .p-4 { padding: calc(var(--spacing) * 4); } .px-2 { padding-inline: calc(var(--spacing) * 2); } .px-4 { padding-inline: calc(var(--spacing) * 4); } .py-1 { padding-block: calc(var(--spacing) * 1); } .text-center { text-align: center; } .text-left { text-align: left; } .font-mono { font-family: var(--font-mono); } .text-2xl { font-size: var(--text-2xl); line-height: var(--tw-leading, var(--text-2xl--line-height)); } .text-base { font-size: var(--text-base); line-height: var(--tw-leading, var(--text-base--line-height)); } .text-lg { font-size: var(--text-lg); line-height: var(--tw-leading, var(--text-lg--line-height)); } .text-sm { font-size: var(--text-sm); line-height: var(--tw-leading, var(--text-sm--line-height)); } .text-xl { font-size: var(--text-xl); line-height: var(--tw-leading, var(--text-xl--line-height)); } .text-xs { font-size: var(--text-xs); line-height: var(--tw-leading, var(--text-xs--line-height)); } .text-\\[10px\\] { font-size: 10px; } .leading-none { --tw-leading: 1; line-height: 1; } .font-bold { --tw-font-weight: var(--font-weight-bold); font-weight: var(--font-weight-bold); } .font-medium { --tw-font-weight: var(--font-weight-medium); font-weight: var(--font-weight-medium); } .font-semibold { --tw-font-weight: var(--font-weight-semibold); font-weight: var(--font-weight-semibold); } .tracking-wide { --tw-tracking: var(--tracking-wide); letter-spacing: var(--tracking-wide); } .tracking-widest { --tw-tracking: var(--tracking-widest); letter-spacing: var(--tracking-widest); } .text-white { color: var(--color-white); } .uppercase { text-transform: uppercase; } .shadow-lg { --tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); } .filter { filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,); } .md\\:gap-5 { @media (width >= 48rem) { gap: calc(var(--spacing) * 5); } } .md\\:rounded-2xl { @media (width >= 48rem) { border-radius: var(--radius-2xl); } } .md\\:rounded-2xl\\! { @media (width >= 48rem) { border-radius: var(--radius-2xl) !important; } } .md\\:text-5xl { @media (width >= 48rem) { font-size: var(--text-5xl); line-height: var(--tw-leading, var(--text-5xl--line-height)); } } .md\\:text-base { @media (width >= 48rem) { font-size: var(--text-base); line-height: var(--tw-leading, var(--text-base--line-height)); } } .md\\:text-sm { @media (width >= 48rem) { font-size: var(--text-sm); line-height: var(--tw-leading, var(--text-sm--line-height)); } } .md\\:text-xl { @media (width >= 48rem) { font-size: var(--text-xl); line-height: var(--tw-leading, var(--text-xl--line-height)); } } .md\\:text-xs { @media (width >= 48rem) { font-size: var(--text-xs); line-height: var(--tw-leading, var(--text-xs--line-height)); } } } @layer components { video::-webkit-media-controls { display: none !important; } video::-webkit-media-controls-panel { display: none !important; } video::-webkit-media-controls-play-button { display: none !important; } video::-webkit-media-controls-timeline { display: none !important; } video::-webkit-media-controls-current-time-display { display: none !important; } video::-webkit-media-controls-time-remaining-display { display: none !important; } video::-webkit-media-controls-mute-button { display: none !important; } video::-webkit-media-controls-volume-slider { display: none !important; } video::-webkit-media-controls-fullscreen-button { display: none !important; } video::-webkit-media-controls-overlay-play-button { display: none !important; } video::-moz-media-controls { display: none !important; } video { outline: none !important; } video[controls] { -webkit-appearance: none !important; appearance: none !important; } video::-webkit-media-controls-enclosure { display: none !important; } video::-webkit-media-controls-start-playback-button { display: none !important; } video[controls]::-webkit-media-controls, video[controls]::-webkit-media-controls-panel, video[controls]::-webkit-media-controls-play-button, video[controls]::-webkit-media-controls-timeline, video[controls]::-webkit-media-controls-current-time-display, video[controls]::-webkit-media-controls-time-remaining-display, video[controls]::-webkit-media-controls-mute-button, video[controls]::-webkit-media-controls-volume-slider, video[controls]::-webkit-media-controls-fullscreen-button, video[controls]::-webkit-media-controls-overlay-play-button, video[controls]::-webkit-media-controls-enclosure, video[controls]::-webkit-media-controls-start-playback-button { display: none !important; visibility: hidden !important; opacity: 0 !important; pointer-events: none !important; } video[controls]::-moz-media-controls { display: none !important; visibility: hidden !important; opacity: 0 !important; } .motto-video-container { position: relative; width: 100%; min-height: 300px; } @supports (aspect-ratio: 16/9) { .motto-video-container { min-height: auto; } } .motto-video-responsive { position: absolute; top: calc(var(--spacing) * 0); left: calc(var(--spacing) * 0); height: 100%; width: 100%; } .motto-skip-button { position: absolute; top: calc(1/2 * 100%); z-index: 10; display: flex; height: calc(var(--spacing) * 16); width: calc(var(--spacing) * 16); --tw-translate-y: calc(calc(1/2 * 100%) * -1); translate: var(--tw-translate-x) var(--tw-translate-y); cursor: pointer; align-items: center; justify-content: center; border-radius: calc(infinity * 1px); border-style: var(--tw-border-style); border-width: 0px; background-color: color-mix(in srgb, #000 70%, transparent); @supports (color: color-mix(in lab, red, red)) { background-color: color-mix(in oklab, var(--color-black) 70%, transparent); } font-size: var(--text-2xl); line-height: var(--tw-leading, var(--text-2xl--line-height)); color: var(--color-white); opacity: 80%; transition-property: all; transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); transition-duration: var(--tw-duration, var(--default-transition-duration)); --tw-duration: 200ms; transition-duration: 200ms; &:hover { @media (hover: hover) { --tw-scale-x: 110%; --tw-scale-y: 110%; --tw-scale-z: 110%; scale: var(--tw-scale-x) var(--tw-scale-y); } } &:hover { @media (hover: hover) { opacity: 100%; } } &:active { --tw-scale-x: 95%; --tw-scale-y: 95%; --tw-scale-z: 95%; scale: var(--tw-scale-x) var(--tw-scale-y); } } .motto-skip-button-back { left: calc(var(--spacing) * 5); } .motto-skip-button-forward { right: calc(var(--spacing) * 5); } } .shaka-seek-bar-container { height: 6px !important; width: 100% !important; margin: 8px 0 !important; border-radius: 4px !important; position: relative !important; border-top: none !important; border-bottom: none !important; box-shadow: none !important; } .shaka-seek-bar { height: 6px !important; width: 100% !important; -webkit-appearance: none !important; appearance: none !important; background: transparent !important; cursor: pointer !important; border: none !important; outline: none !important; position: absolute !important; top: 0 !important; left: 0 !important; border-radius: 4px !important; } .shaka-seek-bar::-webkit-slider-runnable-track { height: 6px !important; background: transparent !important; border-radius: 4px !important; border: none !important; } .shaka-seek-bar::-moz-range-track { height: 6px !important; background: transparent !important; border-radius: 4px !important; border: none !important; } .shaka-seek-bar::-webkit-slider-thumb { -webkit-appearance: none !important; appearance: none !important; width: 16px !important; height: 16px !important; border-radius: 50% !important; background: #ffffff !important; cursor: pointer !important; border: 2px solid #ffffff !important; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3) !important; margin-top: -4px !important; } .shaka-seek-bar::-moz-range-thumb { width: 16px !important; height: 16px !important; border-radius: 50% !important; background: #ffffff !important; cursor: pointer !important; border: 2px solid #ffffff !important; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3) !important; margin-top: -4px !important; } .motto-skip-back-button, .motto-skip-forward-button, .motto-native-skip-button { background: transparent !important; border: none !important; padding: 4px !important; margin: 0px !important; cursor: pointer !important; color: #ffffff !important; transition: all 0.2s ease !important; min-width: 32px !important; height: 32px !important; display: flex !important; align-items: center !important; justify-content: center !important; border-radius: 4px !important; width: 25px; } .motto-skip-back-button:hover, .motto-skip-forward-button:hover, .motto-native-skip-button:hover { opacity: 0.8 !important; background: transparent !important; transform: scale(1.05) !important; } .motto-skip-back-button:active, .motto-skip-forward-button:active, .motto-native-skip-button:active { transform: scale(0.95) !important; } .motto-skip-back-button svg, .motto-skip-forward-button svg, .motto-native-skip-button svg { width: 24px !important; height: 24px !important; } .shaka-spinner-svg { color: white !important; fill: white !important; } .shaka-spinner-path { stroke: white !important; fill: none !important; } .shaka-spinner-container { color: white !important; } .shaka-buffering-spinner { color: white !important; fill: white !important; } .shaka-buffering-spinner svg { color: white !important; fill: white !important; } .shaka-buffering-spinner path { stroke: white !important; fill: none !important; } [data-shaka-player-container] .shaka-spinner, [data-shaka-player-container] .spinner { color: white !important; border-color: white !important; } .material-icons.shaka-spinner { color: white !important; } .shaka-controls-container .shaka-spinner, .shaka-video-container .shaka-spinner { color: white !important; fill: white !important; } .shaka-controls-container .shaka-spinner svg, .shaka-video-container .shaka-spinner svg { color: white !important; fill: white !important; } .shaka-controls-container .shaka-spinner path, .shaka-video-container .shaka-spinner path { stroke: white !important; } .motto-video-loading-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: linear-gradient( 135deg, #1a1a1a 0%, #2d2d2d 100%); display: flex; flex-direction: column; align-items: center; justify-content: center; z-index: 10; transition: opacity 0.3s ease; } .motto-video-loading-overlay.hidden { opacity: 0; pointer-events: none; } .motto-video-loading-content { text-align: center; color: white; } .motto-video-loading-icon { width: 64px; height: 64px; margin-bottom: 16px; opacity: 0.7; } .motto-video-loading-text { font-size: 16px; font-weight: 500; margin-bottom: 8px; } .motto-video-loading-subtext { font-size: 14px; opacity: 0.7; } @keyframes pulse-live { 0% { opacity: 1; transform: scale(1); } 50% { opacity: 0.7; transform: scale(1.1); } 100% { opacity: 1; transform: scale(1); } } .shaka-play-button { background: rgba(255, 255, 255, 0.1) !important; border: none !important; color: white !important; padding: 10px !important; border-radius: 100% !important; transition: all 0.2s ease !important; display: flex !important; align-items: center !important; justify-content: center !important; min-width: 55px !important; height: 55px !important; } .shaka-play-button-container { background: transparent; transition: all 0.2s ease !important; } .motto-video-container:not(.no-cursor) .shaka-play-button-container { background: rgba(0, 0, 0, 0.3); transition: all 0.s ease !important; } .shaka-play-button:hover { background: rgba(255, 255, 255, 0.2) !important; transform: scale(1.05) !important; } .shaka-play-button:active { transform: scale(0.95) !important; } .shaka-play-button > * { display: none !important; } .shaka-play-button::after { content: "" !important; width: 35px !important; height: 35px !important; background-image: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white"><path fill-rule="evenodd" d="M4.5 5.653c0-1.427 1.529-2.33 2.779-1.643l11.54 6.347c1.295.712 1.295 2.573 0 3.286L7.28 19.99c-1.25.687-2.779-.217-2.779-1.643V5.653Z" clip-rule="evenodd" /></svg>') !important; background-repeat: no-repeat !important; background-size: contain !important; background-position: center !important; display: block !important; } .shaka-play-button[aria-label*=Play]::after { background-image: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white"><path fill-rule="evenodd" d="M4.5 5.653c0-1.427 1.529-2.33 2.779-1.643l11.54 6.347c1.295.712 1.295 2.573 0 3.286L7.28 19.99c-1.25.687-2.779-.217-2.779-1.643V5.653Z" clip-rule="evenodd" /></svg>') !important; } .shaka-play-button[aria-label*=Pause]::after { background-image: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white"><path fill-rule="evenodd" d="M6.75 5.25a.75.75 0 0 1 .75-.75H9a.75.75 0 0 1 .75.75v13.5a.75.75 0 0 1-.75.75H7.5a.75.75 0 0 1-.75-.75V5.25Zm7.5 0A.75.75 0 0 1 15 4.5h1.5a.75.75 0 0 1 .75.75v13.5a.75.75 0 0 1-.75.75H15a.75.75 0 0 1-.75-.75V5.25Z" clip-rule="evenodd" /></svg>') !important; } .motto-video-container { background: #111111; } .motto-video-container video { width: 100% !important; height: 100% !important; margin-left: auto !important; margin-right: auto !important; } html[dir=rtl] .shaka-controls-container, html[dir=rtl] .shaka-bottom-controls, html[dir=rtl] .shaka-controls-button-panel { direction: ltr !important; } html[dir=rtl] .shaka-overflow-menu, html[dir=rtl] .shaka-settings-menu { direction: rtl !important; text-align: right !important; } html[dir=rtl] .shaka-overflow-menu .shaka-overflow-button .material-svg-icon:not(:first-child) { margin-right: 12px !important; margin-left: 0 !important; } html[dir=rtl] .shaka-overflow-menu .shaka-overflow-button .material-svg-icon:first-child { margin-right: 0 !important; margin-left: 12px !important; } .shaka-hidden-fast-forward-container, .shaka-hidden-rewind-container { pointer-events: none !important; display: none !important; } @property --tw-gradient-position { syntax: "*"; inherits: false; } @property --tw-gradient-from { syntax: "<color>"; inherits: false; initial-value: #0000; } @property --tw-gradient-via { syntax: "<color>"; inherits: false; initial-value: #0000; } @property --tw-gradient-to { syntax: "<color>"; inherits: false; initial-value: #0000; } @property --tw-gradient-stops { syntax: "*"; inherits: false; } @property --tw-gradient-via-stops { syntax: "*"; inherits: false; } @property --tw-gradient-from-position { syntax: "<length-percentage>"; inherits: false; initial-value: 0%; } @property --tw-gradient-via-position { syntax: "<length-percentage>"; inherits: false; initial-value: 50%; } @property --tw-gradient-to-position { syntax: "<length-percentage>"; inherits: false; initial-value: 100%; } @property --tw-leading { syntax: "*"; inherits: false; } @property --tw-font-weight { syntax: "*"; inherits: false; } @property --tw-tracking { syntax: "*"; inherits: false; } @property --tw-shadow { syntax: "*"; inherits: false; initial-value: 0 0 #0000; } @property --tw-shadow-color { syntax: "*"; inherits: false; } @property --tw-shadow-alpha { syntax: "<percentage>"; inherits: false; initial-value: 100%; } @property --tw-inset-shadow { syntax: "*"; inherits: false; initial-value: 0 0 #0000; } @property --tw-inset-shadow-color { syntax: "*"; inherits: false; } @property --tw-inset-shadow-alpha { syntax: "<percentage>"; inherits: false; initial-value: 100%; } @property --tw-ring-color { syntax: "*"; inherits: false; } @property --tw-ring-shadow { syntax: "*"; inherits: false; initial-value: 0 0 #0000; } @property --tw-inset-ring-color { syntax: "*"; inherits: false; } @property --tw-inset-ring-shadow { syntax: "*"; inherits: false; initial-value: 0 0 #0000; } @property --tw-ring-inset { syntax: "*"; inherits: false; } @property --tw-ring-offset-width { syntax: "<length>"; inherits: false; initial-value: 0px; } @property --tw-ring-offset-color { syntax: "*"; inherits: false; initial-value: #fff; } @property --tw-ring-offset-shadow { syntax: "*"; inherits: false; initial-value: 0 0 #0000; } @property --tw-blur { syntax: "*"; inherits: false; } @property --tw-brightness { syntax: "*"; inherits: false; } @property --tw-contrast { syntax: "*"; inherits: false; } @property --tw-grayscale { syntax: "*"; inherits: false; } @property --tw-hue-rotate { syntax: "*"; inherits: false; } @property --tw-invert { syntax: "*"; inherits: false; } @property --tw-opacity { syntax: "*"; inherits: false; } @property --tw-saturate { syntax: "*"; inherits: false; } @property --tw-sepia { syntax: "*"; inherits: false; } @property --tw-drop-shadow { syntax: "*"; inherits: false; } @property --tw-drop-shadow-color { syntax: "*"; inherits: false; } @property --tw-drop-shadow-alpha { syntax: "<percentage>"; inherits: false; initial-value: 100%; } @property --tw-drop-shadow-size { syntax: "*"; inherits: false; } @property --tw-translate-x { syntax: "*"; inherits: false; initial-value: 0; } @property --tw-translate-y { syntax: "*"; inherits: false; initial-value: 0; } @property --tw-translate-z { syntax: "*"; inherits: false; initial-value: 0; } @property --tw-border-style { syntax: "*"; inherits: false; initial-value: solid; } @property --tw-duration { syntax: "*"; inherits: false; } @property --tw-scale-x { syntax: "*"; inherits: false; initial-value: 1; } @property --tw-scale-y { syntax: "*"; inherits: false; initial-value: 1; } @property --tw-scale-z { syntax: "*"; inherits: false; initial-value: 1; } @keyframes spin { to { transform: rotate(360deg); } } @keyframes pulse { 50% { opacity: 0.5; } } @layer properties { @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) { *, ::before, ::after, ::backdrop { --tw-gradient-position: initial; --tw-gradient-from: #0000; --tw-gradient-via: #0000; --tw-gradient-to: #0000; --tw-gradient-stops: initial; --tw-gradient-via-stops: initial; --tw-gradient-from-position: 0%; --tw-gradient-via-position: 50%; --tw-gradient-to-position: 100%; --tw-leading: initial; --tw-font-weight: initial; --tw-tracking: initial; --tw-shadow: 0 0 #0000; --tw-shadow-color: initial; --tw-shadow-alpha: 100%; --tw-inset-shadow: 0 0 #0000; --tw-inset-shadow-color: initial; --tw-inset-shadow-alpha: 100%; --tw-ring-color: initial; --tw-ring-shadow: 0 0 #0000; --tw-inset-ring-color: initial; --tw-inset-ring-shadow: 0 0 #0000; --tw-ring-inset: initial; --tw-ring-offset-width: 0px; --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-blur: initial; --tw-brightness: initial; --tw-contrast: initial; --tw-grayscale: initial; --tw-hue-rotate: initial; --tw-invert: initial; --tw-opacity: initial; --tw-saturate: initial; --tw-sepia: initial; --tw-drop-shadow: initial; --tw-drop-shadow-color: initial; --tw-drop-shadow-alpha: 100%; --tw-drop-shadow-size: initial; --tw-translate-x: 0; --tw-translate-y: 0; --tw-translate-z: 0; --tw-border-style: solid; --tw-duration: initial; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scale-z: 1; } } } `); // src/Player.tsx import { forwardRef, useEffect as useEffect5, useRef as useRef9, useImperativeHandle, useCallback as useCallback9, useState as useState4 } from "react"; import shaka3 from "shaka-player/dist/shaka-player.ui"; // src/hooks/useShakaPlayer.ts import { useRef, useCallback, useState } from "react"; import shaka from "shaka-player/dist/shaka-player.ui"; // src/utils/devices.ts var isAppleDevice = () => { if (typeof navigator === "undefined") return false; const ua = navigator.userAgent || ""; const isIOS = /iPad|iPhone|iPod/.test(ua) || navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1; const isSafari = /Safari/.test(ua) && !/Chrome|CriOS|FxiOS|Edg/.test(ua); const isMacSafari = /Macintosh/.test(ua) && isSafari; return isIOS || isMacSafari; }; var isPlayReadySupported = () => { if (typeof navigator === "undefined" || typeof window === "undefined") { return false; } if (!navigator.requestMediaKeySystemAccess) { return false; } const userAgent = navigator.userAgent || ""; const isXbox = /Xbox/.test(userAgent); const isEdge = /Edg/.test(userAgent); const isIE = /Trident|MSIE/.test(userAgent); const isWindows = /Windows/.test(userAgent); return isXbox || (isEdge || isIE) && isWindows; }; var supportsWidevinePersistentLicenses = () => { return false; if (typeof navigator === "undefined") { return false; } const userAgent = navigator.userAgent || ""; const isChromeMatch = userAgent.match(/Chrome\/(\d+)/); if (!isChromeMatch) { return false; } if (/Edg|OPR|Opera/.test(userAgent)) { return false; } const chromeVersion = parseInt(isChromeMatch[1], 10); if (chromeVersion < 64) { return false; } const isMacOS = /Mac OS X|Macintosh/.test(userAgent); const isWindows = /Windows/.test(userAgent); return isMacOS || isWindows; }; // src/hooks/useShakaPlayer.ts import initShakaPlayerMux from "@mux/mux-data-shakaplayer"; // package.json var version = "1.0.1-rc.73"; // src/utils/licenseCache.ts var PERSISTENT_LICENSE_PREFIX = "motto_lic_"; var LICENSE_EXPIRY_MS = 2 * 60 * 60 * 1e3; var LICENSE_MAX_RETRY_ATTEMPTS = 10; var getAllLicenseCacheKeys = () => { const keys = []; for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); if (key?.startsWith(PERSISTENT_LICENSE_PREFIX)) { keys.push(key); } } return keys; }; var getAllLicenseCacheEntries = () => { const keys = getAllLicenseCacheKeys(); const entries = []; for (const key of keys) { try { const stored = localStorage.getItem(key); if (stored) { const data = JSON.parse(stored); entries.push({ key, data }); } } catch (error) { console.warn(`Failed to parse license cache entry for key ${key}:`, error); localStorage.removeItem(key); } } return entries; }; var evictLRUEntry = () => { const entries = getAllLicenseCacheEntries(); if (entries.length === 0) { return false; } entries.sort((a, b) => { const oldestA = Math.min(...a.data.map((session) => session.timestamp)); const oldestB = Math.min(...b.data.map((session) => session.timestamp)); return oldestA - oldestB; }); const lruEntry = entries[0]; localStorage.removeItem(lruEntry.key); console.log(`Evicted LRU license cache entry: ${lruEntry.key}`); return true; }; var storeWithQuotaHandling = (key, data) => { let attempts = 0; while (attempts < LICENSE_MAX_RETRY_ATTEMPTS) { try { localStorage.setItem(key, data); return true; } catch (error) { if (error instanceof DOMException && (error.code === 22 || // QUOTA_EXCEEDED_ERR error.name === "QuotaExceededError" || error.name === "NS_ERROR_DOM_QUOTA_REACHED")) { console.warn(`QuotaExceededError on attempt ${attempts + 1}, attempting LRU eviction...`); if (!evictLRUEntry()) { console.error("No more entries to evict, storage operation failed"); return false; } attempts++; } else { console.error("Failed to store license cache data:", error); return false; } } } console.error(`Failed to store license cache data after ${LICENSE_MAX_RETRY_ATTEMPTS} eviction attempts`); return false; }; var managePersistentLicenseStorage = (playlistId, licenseCacheKey, newSessionMetadata) => { try { const storageKey = `${PERSISTENT_LICENSE_PREFIX}${playlistId}_${licenseCacheKey}`; const stored = localStorage.getItem(storageKey); let existingSessions = []; if (stored) { try { existingSessions = JSON.parse(stored); } catch (parseError) { console.warn("Failed to parse existing persistent license data:", parseError); existingSessions = []; } } const now = Date.now(); let validSessions = existingSessions.filter( (session) => now - session.timestamp < LICENSE_EXPIRY_MS ); if (newSessionMetadata && newSessionMetadata.length > 0) { const newSessions = newSessionMetadata.map((session) => { const uint8Array = new Uint8Array(session.initData); const binaryString = Array.from(uint8Array).map((byte) => String.fromCharCode(byte)).join(""); return { sessionId: session.sessionId, initData: btoa(binaryString), initDataType: session.initDataType, keySystem: session.keySystem, timestamp: now }; }); const newSessionIds = new Set(newSessions.map((s) => s.sessionId)); validSessions = validSessions.filter((session) => !newSessionIds.has(session.sessionId)); validSessions = [...validSessions, ...newSessions]; } if (validSessions.length === 0) { localStorage.removeItem(storageKey); } else { const dataToStore = JSON.stringify(validSessions); const success = storeWithQuotaHandling(storageKey, dataToStore); if (!success) { console.error(`Failed to store license cache for ${storageKey} after quota handling`); return validSessions; } } return validSessions; } catch (error) { console.warn("Failed to manage persistent license storage:", error); return []; } }; var storePersistentLicense = (playlistId, licenseCacheKey, sessionMetadata) => { managePersistentLicenseStorage(playlistId, licenseCacheKey, sessionMetadata); }; var retrievePersistentLicense = (playlistId, licenseCacheKey) => { try { const validSessions = managePersistentLicenseStorage(playlistId, licenseCacheKey); return validSessions.map((session) => ({ sessionId: session.sessionId, initData: Uint8Array.from(atob(session.initData), (c) => c.charCodeAt(0)).buffer, initDataType: session.initDataType, keySystem: session.keySystem })); } catch (error) { console.warn("Failed to retrieve persistent license:", error); return []; } }; var clearAllPersistentLicenses = () => { try { const keys = getAllLicenseCacheKeys(); for (const key of keys) { localStorage.removeItem(key); } console.log(`Cleared ${keys.length} persistent license cache entries`); return keys.length; } catch (error) { console.error("Failed to clear persistent licenses:", error); return 0; } }; // src/hooks/useShakaPlayer.ts var useShakaPlayer = ({ src, shakaConfig, drmConfig, onError, onPlayerReady, muxConfig, onMuxReady, onMuxDataUpdate, publicKey, mottoToken, hasAds = false }) => { const playerRef = useRef(null); const [isRetrying, setIsRetrying] = useState(false); const videoElementRef = useRef(null); const destroyInProgressRef = useRef(null); const isDestroyingRef = useRef(false); const initSequenceRef = useRef(0); const activeInitIdRef = useRef(null); const waitingForKeyTimerRef = useRef(null); const waitingForKeyHandlerRef = useRef(null); const playbackResumedHandlerRef = useRef(null); const usingPersistentLicenseRef = useRef(false); const storedPersistentThisLoadRef = useRef(false); const drmExpirationHandlerRef = useRef(null); const getManifestUrl = useCallback(() => { let manifestUrl = src.url; const isDRM = Boolean(src.drm); if (isDRM) { const isPlayReady = isPlayReadySupported(); if (isAppleDevice() && src.drm.fairplay?.certificateUrl) { manifestUrl = src.drm.fairplay.playlistUrl; } else if (isPlayReady && src.drm.playready?.licenseUrl) { manifestUrl = src.drm.playready.playlistUrl; } else { manifestUrl = src.drm?.widevine?.playlistUrl || ""; } } return manifestUrl; }, [src]); const initializePlayerInternal = useCallback(async (video) => { try { if (destroyInProgressRef.current) { try { await destroyInProgressRef.current; } catch { } } const myInitId = ++initSequenceRef.current; activeInitIdRef.current = myInitId; videoElementRef.current = video; shaka.polyfill.installAll(); if (!shaka.Player.isBrowserSupported()) { throw new Error("Browser not supported by Shaka Player"); } if (isDestroyingRef.current) { return; } const player = new shaka.Player(); playerRef.current = player; await player.attach(video); const defaultConfig = { manifest: { // Override availability window to allow DVR window to grow from start // Set to a very large value (24 hours in seconds) to effectively allow unlimited growth availabilityWindowOverride: 86400 // 24 hours in seconds }, streaming: { // Allow seeking to any point within the availability window safeSeekOffset: 5, // 5 seconds from live edge to prevent buffering // Increase tolerance for manifest timing inaccuracies in live streams inaccurateManifestTolerance: 2 } }; player.configure(defaultConfig); if (shakaConfig) { player.configure(shakaConfig); } const manifestUrl = getManifestUrl(); let playlistId = src.id; const isDRM = Boolean(src.drm); storedPersistentThisLoadRef.current = false; if (activeInitIdRef.current !== myInitId || isDestroyingRef.current) { try { await player.destroy(); } catch { } if (playerRef.current === player) playerRef.current = null; return; } let storedSessionsMetadata = []; if (isDRM && playlistId) { storedSessionsMetadata = retrievePersistentLicense(playlistId, src.drm.licenseCacheKey ?? ""); } if (isDRM) { const drmConfig2 = { servers: { "com.widevine.alpha": src.drm.widevine?.licenseUrl, "com.microsoft.playready": src.drm.playready?.licenseUrl, "com.apple.fps": src.drm.fairplay?.licenseUrl }, keySystemsMapping: { // Fall back or reroute to the platform’s recommended PlayReady CDM if needed "com.microsoft.playready": "com.microsoft.playready.recommendation" }, ...src.drm.fairplay && { advanced: { "com.apple.fps": { serverCertificateUri: src.drm.fairplay.certificateUrl } } } }; drmConfig2.advanced = { ...drmConfig2.advanced, "com.widevine.alpha": { ...drmConfig2.advanced?.["com.widevine.alpha"], sessionType: supportsWidevinePersistentLicenses() ? "persistent-license" : "temporary" }, "com.microsoft.playready": { ...drmConfig2.advanced?.["com.microsoft.playready"], sessionType: "persistent-license" // PlayReady seems to always require persistent-license (temporary won't work, not compatible with license server response) }, "com.apple.fps": { ...drmConfig2.advanced?.["com.apple.fps"], sessionType: "temporary" // FairPlay always uses temporary sessions - Safari won't play with persistent-license } }; if (storedSessionsMetadata.length > 0) { drmConfig2.persistentSessionOnlinePlayback = true; drmConfig2.persistentSessionsMetadata = storedSessionsMetadata.map((session) => ({ sessionId: session.sessionId, initData: new Uint8Array(session.initData), initDataType: session.initDataType })); } usingPersistentLicenseRef.current = storedSessionsMetadata.length > 0; player.configure({ drm: drmConfig2 }); const clearWaitingForKeyTimer = () => { if (waitingForKeyTimerRef.current !== null) { window.clearTimeout(waitingForKeyTimerRef.current); waitingForKeyTimerRef.current = null; } }; const onPlaybackResumed = () => { clearWaitingForKeyTimer(); }; const onWaitingForKey = () => { if (!usingPersistentLicenseRef.current) return; if (isRetrying) return; clearWaitingForKeyTimer(); waitingForKeyTimerRef.current = window.setTimeout(async () => { try { if (isRetrying) return; console.warn("Stuck waiting for decryption key; attempting license cache reset and reload..."); const clearedCount = clearAllPersistentLicenses(); if (clearedCount > 0 && playerRef.current) { setIsRetrying(true); await playerRef.current.load(getManifestUrl()); console.log("Reloaded manifest after clearing cached licenses"); } else { console.warn("No cached licenses found to clear, skipping reload"); } } catch (e) { console.error("Failed during recovery from waitingforkey state:", e); onError?.(e); } finally { setIsRetrying(false); clearWaitingForKeyTimer(); } }, 3e3); }; try { video.addEventListener("waitingforkey", onWaitingForKey); video.addEventListener("playing", onPlaybackResumed); video.addEventListener("ended", onPlaybackResumed); video.addEventListener("pause", onPlaybackResumed); waitingForKeyHandlerRef.current = onWaitingForKey; playbackResumedHandlerRef.current = onPlaybackResumed; } catch (e) { console.warn("Failed to attach waitingforkey/playback listeners:", e); } const netEngine = player.getNetworkingEngine(); if (netEngine) { netEngine.registerRequestFilter((type, request) => { switch (type) { case shaka.net.NetworkingEngine.RequestType.LICENSE: if (publicKey) { request.headers["authorization"] = `Bearer ${publicKey}`; } if (mottoToken) { request.headers["x-motto-token"] = mottoToken; } break; case shaka.net.NetworkingEngine.RequestType.MANIFEST: case shaka.net.NetworkingEngine.RequestType.SEGMENT: request.allowCrossSiteCredentials = true; break; } }); netEngine.registerResponseFilter((type, response) => { if (type === shaka.net.NetworkingEngine.RequestType.LICENSE) { const ks = player.keySystem && player.keySystem(); if (ks === "com.apple.fps") { const responseText = shaka.util.StringUtils.fromUTF8(response.data); response.data = shaka.util.Uint8ArrayUtils.fromBase64(responseText).buffer; } } }); } } if (isDRM && playlistId) { const onDRMSessionUpdate = () => { try { if (storedPersistentThisLoadRef.current) return; const activeDrmSessions = player.getActiveSessionsMetadata?.(); if (!activeDrmSessions) return; const persistentSessions = activeDrmSessions.filter( (session) => session.sessionType === "persistent-license" ); if (persistentSessions.length > 0) { const sessionsToStore = persistentSessions.map((session) => ({ sessionId: session.sessionId, initData: session.initData, initDataType: session.initDataType, keySystem: session.keySystem || "com.widevine.alpha" })); storePersistentLicense(playlistId, src.drm.licenseCacheKey ?? "", sessionsToStore); storedPersistentThisLoadRef.current = true; } } catch (e) { console.warn("Failed to persist licenses on expiration update:", e); } }; try { player.addEventListener("drmsessionupdate", onDRMSessionUpdate); drmExpirationHandlerRef.current = onDRMSessionUpdate; } catch (e) { console.warn("Failed to attach drmsessionupdate listener:", e); } } player?.addEventListener("error", (event) => { const error = event.detail; if (error?.code === 7e3) { return; } if (error?.code >= 6e3 && error?.code < 7e3 && !isRetrying && videoElementRef.current) { console.warn(`DRM error detected (code: ${error.code}), checking for cached licenses...`); const clearedCount = clearAllPersistentLicenses(); if (clearedCount > 0) { console.warn(`Cleared ${clearedCount} cached licenses, retrying...`); setIsRetrying(true); setTimeout(async () => { try { const video2 = videoElementRef.current; const currentPlayer = playerRef.current; if (video2 && currentPlayer) { console.log("Reloading manifest after license cache clear..."); await currentPlayer.load(getManifestUrl()); console.log("Manifest reloaded successfully"); } } catch (retryError) { console.error("Failed to retry after license clear:", retryError); onError?.(retryError); } finally { setIsRetrying(false); } }, 500); return; }