From d7a93cbbb4ca09665401b51fb20de202bdf73735 Mon Sep 17 00:00:00 2001 From: Jeroen Tempels Date: Thu, 14 Mar 2024 13:36:15 +0100 Subject: [PATCH 01/20] Make DefaultUI extendable --- src/DefaultUI.ts | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/src/DefaultUI.ts b/src/DefaultUI.ts index ae218761..ba249b66 100644 --- a/src/DefaultUI.ts +++ b/src/DefaultUI.ts @@ -86,9 +86,10 @@ export class DefaultUI extends HTMLElement { ]; } - private readonly _ui: UIContainer; - private readonly _titleSlot: HTMLSlotElement; - private readonly _timeRange: TimeRange; + protected readonly _shadowRoot: ShadowRoot; + protected readonly _ui: UIContainer; + private readonly _titleSlot: HTMLSlotElement | undefined; + private readonly _timeRange: TimeRange | undefined; private _appliedExtensions: boolean = false; /** @@ -101,18 +102,17 @@ export class DefaultUI extends HTMLElement { */ constructor(configuration: PlayerConfiguration = {}) { super(); - const shadowRoot = this.attachShadow({ mode: 'open', delegatesFocus: true }); - shadowRoot.appendChild(template().content.cloneNode(true)); + this._shadowRoot = this.initShadowRoot(); - this._ui = shadowRoot.querySelector('theoplayer-ui')!; + this._ui = this._shadowRoot.querySelector('theoplayer-ui')!; this._ui.addEventListener(READY_EVENT, this._dispatchReadyEvent); this._ui.addEventListener(STREAM_TYPE_CHANGE_EVENT, this._updateStreamType); this.setConfiguration_(configuration); - this._titleSlot = shadowRoot.querySelector('slot[name="title"]')!; - this._titleSlot.addEventListener('slotchange', this._onTitleSlotChange); + this._titleSlot = this._shadowRoot.querySelector('slot[name="title"]') ?? undefined; + this._titleSlot?.addEventListener('slotchange', this._onTitleSlotChange); - this._timeRange = shadowRoot.querySelector('theoplayer-time-range')!; + this._timeRange = this._shadowRoot.querySelector('theoplayer-time-range') ?? undefined; this._upgradeProperty('configuration'); this._upgradeProperty('source'); @@ -124,6 +124,12 @@ export class DefaultUI extends HTMLElement { this._upgradeProperty('dvrThreshold'); } + protected initShadowRoot(): ShadowRoot { + const shadowRoot = this.attachShadow({ mode: 'open', delegatesFocus: true }); + shadowRoot.appendChild(template().content.cloneNode(true)); + return shadowRoot; + } + private _upgradeProperty(prop: keyof this) { if (this.hasOwnProperty(prop)) { let value = this[prop]; @@ -304,8 +310,10 @@ export class DefaultUI extends HTMLElement { private readonly _updateStreamType = () => { this.setAttribute(Attribute.STREAM_TYPE, this.streamType); - // Hide seekbar when stream is live with no DVR - toggleAttribute(this._timeRange, Attribute.HIDDEN, this.streamType === 'live'); + if (this._timeRange) { + // Hide seekbar when stream is live with no DVR + toggleAttribute(this._timeRange, Attribute.HIDDEN, this.streamType === 'live'); + } }; private readonly _dispatchReadyEvent = () => { @@ -313,7 +321,9 @@ export class DefaultUI extends HTMLElement { }; private readonly _onTitleSlotChange = () => { - toggleAttribute(this, Attribute.HAS_TITLE, this._titleSlot.assignedNodes().length > 0); + if (this._titleSlot) { + toggleAttribute(this, Attribute.HAS_TITLE, this._titleSlot.assignedNodes().length > 0); + } }; } From 3937c0dfd32786d50a178c92eef973919259be99 Mon Sep 17 00:00:00 2001 From: Jeroen Tempels Date: Thu, 14 Mar 2024 15:29:30 +0100 Subject: [PATCH 02/20] Allow setting list of target qualities --- src/components/QualityRadioButton.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/components/QualityRadioButton.ts b/src/components/QualityRadioButton.ts index 61d86d5d..b18f1a88 100644 --- a/src/components/QualityRadioButton.ts +++ b/src/components/QualityRadioButton.ts @@ -75,11 +75,18 @@ export class QualityRadioButton extends RadioButton { private readonly _updateFromTrack = () => { if (this._track !== undefined) { - const targetQualities = this._track.targetQuality; + let targetQualities = this._track.targetQuality; + if (Array.isArray(targetQualities)) { + if (targetQualities.length === 1) { + targetQualities = targetQualities[0] + } else { + targetQualities = undefined + } + } if (this._quality === undefined) { this.checked = targetQualities === undefined; } else { - this.checked = Array.isArray(targetQualities) ? targetQualities.indexOf(this._quality) >= 0 : targetQualities === this._quality; + this.checked = targetQualities === this._quality; } } this._updateFromQuality(); From c76cf8805e8d283246a5b934ce73582bf4fa36ea Mon Sep 17 00:00:00 2001 From: Jeroen Tempels Date: Mon, 18 Mar 2024 16:38:00 +0100 Subject: [PATCH 03/20] Add THEOlive UI --- src/THEOliveUI.css | 228 ++++++++++++++++++ src/THEOliveUI.html | 30 +++ src/THEOliveUI.ts | 116 +++++++++ src/UIContainer.ts | 1 + src/components/theolive/Logo.ts | 53 ++++ src/components/theolive/icons/settings.svg | 1 + src/components/theolive/icons/warning.svg | 3 + .../quality/AbstractQualitySelector.ts | 81 +++++++ .../quality/AutomaticQualitySelector.ts | 37 +++ .../theolive/quality/BadNetworkModeButton.css | 24 ++ .../theolive/quality/BadNetworkModeButton.ts | 93 +++++++ .../theolive/quality/BadNetworkModeMenu.css | 9 + .../theolive/quality/BadNetworkModeMenu.ts | 57 +++++ .../quality/BadNetworkModeSelector.ts | 37 +++ src/index.ts | 1 + 15 files changed, 771 insertions(+) create mode 100644 src/THEOliveUI.css create mode 100644 src/THEOliveUI.html create mode 100644 src/THEOliveUI.ts create mode 100644 src/components/theolive/Logo.ts create mode 100644 src/components/theolive/icons/settings.svg create mode 100644 src/components/theolive/icons/warning.svg create mode 100644 src/components/theolive/quality/AbstractQualitySelector.ts create mode 100644 src/components/theolive/quality/AutomaticQualitySelector.ts create mode 100644 src/components/theolive/quality/BadNetworkModeButton.css create mode 100644 src/components/theolive/quality/BadNetworkModeButton.ts create mode 100644 src/components/theolive/quality/BadNetworkModeMenu.css create mode 100644 src/components/theolive/quality/BadNetworkModeMenu.ts create mode 100644 src/components/theolive/quality/BadNetworkModeSelector.ts diff --git a/src/THEOliveUI.css b/src/THEOliveUI.css new file mode 100644 index 00000000..b4d1862c --- /dev/null +++ b/src/THEOliveUI.css @@ -0,0 +1,228 @@ +:host { + box-sizing: border-box; + position: relative; + display: inline-block; + width: 100%; + font-family: Helvetica, Arial, sans-serif; +} + +:host([hidden]) { + display: none !important; +} + +theoplayer-ui { + font-family: Helvetica, Arial, sans-serif; + width: 100%; + --theoplayer-loading-delay: 0.1s; +} + +:host(:fullscreen), +:host(:fullscreen) theoplayer-ui { + width: 100% !important; + height: 100% !important; +} + +theoplayer-menu::part(heading) { + display: none !important; +} + +[part='title'] { + user-select: none; + color: var(--theoplayer-text-color, #fff); + text-shadow: 0 0 4px rgba(0, 0, 0, 0.75); + padding: var(--theoplayer-control-padding, 10px); + + /* Vertically center any text */ + font-size: var(--theoplayer-text-font-size, 14px); + line-height: var(--theoplayer-text-content-height, var(--theoplayer-control-height, 24px)); +} + +:host(:not([has-title])) [part='title'] { + display: none; +} + +[part='centered-chrome'] * { + --theoplayer-control-height: 48px; +} + +[part='middle-chrome'] { + position: relative; + display: flex; + flex-flow: column nowrap; + /* Align to bottom for Chromecast display */ + justify-content: flex-end; + flex-grow: 1; + pointer-events: none; +} + +[part='bottom-chrome'] { + position: relative; + display: flex; + flex-flow: column nowrap; + align-items: stretch; +} + +/* + * On mobile, put a backdrop color on the entire player when showing controls. + */ +:host([mobile]) theoplayer-ui { + --theoplayer-control-backdrop-background: rgba(0, 0, 0, 0.5); +} + +/* + * On desktop, put a soft gradient behind the top and bottom control bars. + */ +:host { + /* + * Smooth transparent-to-black gradient from Chrome's