Skip to content

Commit

Permalink
Merge pull request #40 from THEOplayer/tv
Browse files Browse the repository at this point in the history
Smart TV support
  • Loading branch information
Jeroen-Veltmans authored Nov 3, 2023
2 parents 6d83e9f + 5041068 commit 1905aee
Show file tree
Hide file tree
Showing 34 changed files with 579 additions and 250 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@
> - 🏠 Internal
> - 💅 Polish
## Unreleased

- 🚀 Added support for smart TVs. ([#40](https://github.com/THEOplayer/web-ui/pull/40))
- Updated `<theoplayer-default-ui>` to automatically switch to an optimized layout when running on a smart TV.
For custom UIs using `<theoplayer-ui>`, you can use the `tv-only` and `tv-hidden` attributes to show or hide specific UI elements on smart TVs.
- Added support for navigating the UI using a TV remote control.
- Added a `tv-focus` attribute to specify which UI element should receive the initial focus when showing the controls on a TV.
In the default UI, initial focus is on the seek bar.

## v1.4.0 (2023-10-04)

- 💥 **Breaking Change**: This project now requires THEOplayer version 6.0.0 or higher.
Expand Down
6 changes: 1 addition & 5 deletions docs/examples/custom-ui.html
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,6 @@
<script>
var ui = document.querySelector('theoplayer-ui');
document.querySelector('#mobile-toggle').addEventListener('change', function (ev) {
if (ev.target.checked) {
ui.setAttribute('mobile', '');
} else {
ui.removeAttribute('mobile');
}
ui.setAttribute('device-type', ev.target.checked ? 'mobile' : 'desktop');
});
</script>
19 changes: 13 additions & 6 deletions docs/examples/default-ui.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,22 @@
<span slot="title">Elephant's Dream</span>
</theoplayer-default-ui>
<p>
<label style="user-select: none"><input type="checkbox" id="mobile-toggle" /> Mobile</label>
<label style="user-select: none">
Override device type
<select id="device-type-select">
<option value=""></option>
<option value="desktop">Desktop</option>
<option value="mobile">Mobile</option>
<option value="tv">TV</option>
</select>
</label>
</p>
<script>
var ui = document.querySelector('theoplayer-default-ui');
document.querySelector('#mobile-toggle').addEventListener('change', function (ev) {
if (ev.target.checked) {
ui.setAttribute('mobile', '');
} else {
ui.removeAttribute('mobile');
document.querySelector('#device-type-select').addEventListener('change', function (ev) {
var selectedDeviceType = ev.target.value;
if (selectedDeviceType) {
ui.setAttribute('device-type', selectedDeviceType);
}
});
</script>
10 changes: 10 additions & 0 deletions src/DefaultUI.css
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,16 @@ theoplayer-ad-skip-button:not([disabled]) {
display: none !important;
}

/*
* TV-only and TV-hidden elements
*/
:host([tv]) [tv-hidden],
:host([tv]) theoplayer-control-bar ::slotted([tv-hidden]),
:host(:not([tv])) [tv-only],
:host(:not([tv])) theoplayer-control-bar ::slotted([tv-only]) {
display: none !important;
}

/*
* Live-only and live-hidden elements
*/
Expand Down
12 changes: 6 additions & 6 deletions src/DefaultUI.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,20 @@
<theoplayer-control-bar>
<theoplayer-play-button mobile-hidden ad-only class="theoplayer-ad-control"></theoplayer-play-button>
<theoplayer-mute-button ad-only class="theoplayer-ad-control"></theoplayer-mute-button>
<theoplayer-time-range show-ad-markers class="theoplayer-ad-control"></theoplayer-time-range>
<theoplayer-time-range show-ad-markers tv-focus class="theoplayer-ad-control"></theoplayer-time-range>
</theoplayer-control-bar>
<theoplayer-control-bar ad-hidden>
<theoplayer-play-button mobile-hidden></theoplayer-play-button>
<theoplayer-mute-button></theoplayer-mute-button>
<theoplayer-volume-range mobile-hidden></theoplayer-volume-range>
<theoplayer-mute-button tv-hidden></theoplayer-mute-button>
<theoplayer-volume-range mobile-hidden tv-hidden></theoplayer-volume-range>
<theoplayer-live-button live-only ad-hidden></theoplayer-live-button>
<theoplayer-time-display show-duration remaining-when-live></theoplayer-time-display>
<span class="theoplayer-spacer" style="pointer-events: auto"></span>
<theoplayer-language-menu-button menu="language-menu" mobile-hidden ad-hidden></theoplayer-language-menu-button>
<theoplayer-airplay-button mobile-hidden ad-hidden></theoplayer-airplay-button>
<theoplayer-chromecast-button mobile-hidden ad-hidden></theoplayer-chromecast-button>
<theoplayer-airplay-button tv-hidden mobile-hidden ad-hidden></theoplayer-airplay-button>
<theoplayer-chromecast-button tv-hidden mobile-hidden ad-hidden></theoplayer-chromecast-button>
<slot name="bottom-control-bar"></slot>
<theoplayer-fullscreen-button></theoplayer-fullscreen-button>
<theoplayer-fullscreen-button tv-hidden></theoplayer-fullscreen-button>
</theoplayer-control-bar>
</div>
<theoplayer-language-menu id="language-menu" slot="menu" hidden></theoplayer-language-menu>
Expand Down
45 changes: 18 additions & 27 deletions src/DefaultUI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import defaultUiCss from './DefaultUI.css';
import defaultUiHtml from './DefaultUI.html';
import { Attribute } from './util/Attribute';
import { applyExtensions } from './extensions/ExtensionRegistry';
import { isMobile } from './util/Environment';
import { isMobile, isTv } from './util/Environment';
import type { DeviceType } from './util/DeviceType';
import type { StreamType } from './util/StreamType';
import type { TimeRange } from './components/TimeRange';
import { STREAM_TYPE_CHANGE_EVENT } from './events/StreamTypeChangeEvent';
import { toggleAttribute } from './util/CommonUtils';

const template = document.createElement('template');
template.innerHTML = `<style>${defaultUiCss}</style>${defaultUiHtml}`;
Expand Down Expand Up @@ -40,8 +42,10 @@ shadyCss.prepareTemplate(template, 'theoplayer-default-ui');
* @attribute `fluid` - If set, the player automatically adjusts its height to fit the video's aspect ratio.
* @attribute `muted` - If set, the player starts out as muted. Reflects `ui.player.muted`.
* @attribute `autoplay` - If set, the player attempts to automatically start playing (if allowed).
* @attribute `mobile` - Whether to use a mobile-optimized UI layout instead.
* Can be used in CSS to show/hide certain desktop-specific or mobile-specific UI controls.
* @attribute `device-type` - The device type, either "desktop", "mobile" or "tv".
* Can be used in CSS to show/hide certain device-specific UI controls.
* @attribute `mobile` - Whether the user is on a mobile device. Equivalent to `device-type == "mobile"`.
* @attribute `tv` - Whether the user is on a TV device. Equivalent to `device-type == "tv"`.
* @attribute `stream-type` - The stream type, either "vod", "live" or "dvr".
* Can be used to show/hide certain UI controls specific for livestreams, such as
* a {@link LiveButton | `<theoplayer-live-button>`}.
Expand All @@ -65,7 +69,7 @@ export class DefaultUI extends HTMLElement {
Attribute.MUTED,
Attribute.AUTOPLAY,
Attribute.FLUID,
Attribute.MOBILE,
Attribute.DEVICE_TYPE,
Attribute.STREAM_TYPE,
Attribute.USER_IDLE_TIMEOUT,
Attribute.DVR_THRESHOLD,
Expand Down Expand Up @@ -227,8 +231,9 @@ export class DefaultUI extends HTMLElement {
connectedCallback(): void {
shadyCss.styleElement(this);

if (!this.hasAttribute(Attribute.MOBILE) && isMobile()) {
this.setAttribute(Attribute.MOBILE, '');
if (!this.hasAttribute(Attribute.DEVICE_TYPE)) {
const deviceType: DeviceType = isMobile() ? 'mobile' : isTv() ? 'tv' : 'desktop';
this.setAttribute(Attribute.DEVICE_TYPE, deviceType);
}

if (!this._appliedExtensions) {
Expand Down Expand Up @@ -258,17 +263,11 @@ export class DefaultUI extends HTMLElement {
} else if (attrName === Attribute.AUTOPLAY) {
this.autoplay = hasValue;
} else if (attrName === Attribute.FLUID) {
if (hasValue) {
this._ui.setAttribute(Attribute.FLUID, newValue);
} else {
this._ui.removeAttribute(Attribute.FLUID);
}
} else if (attrName === Attribute.MOBILE) {
if (hasValue) {
this._ui.setAttribute(Attribute.MOBILE, newValue);
} else {
this._ui.removeAttribute(Attribute.MOBILE);
}
toggleAttribute(this._ui, Attribute.FLUID, hasValue);
} else if (attrName === Attribute.DEVICE_TYPE) {
toggleAttribute(this, Attribute.MOBILE, newValue === 'mobile');
toggleAttribute(this, Attribute.TV, newValue === 'tv');
this._ui.setAttribute(Attribute.DEVICE_TYPE, newValue);
} else if (attrName === Attribute.STREAM_TYPE) {
this.streamType = newValue;
} else if (attrName === Attribute.USER_IDLE_TIMEOUT) {
Expand All @@ -284,19 +283,11 @@ export class DefaultUI extends HTMLElement {
private readonly _updateStreamType = () => {
this.setAttribute(Attribute.STREAM_TYPE, this.streamType);
// Hide seekbar when stream is live with no DVR
if (this.streamType === 'live') {
this._timeRange.setAttribute(Attribute.HIDDEN, '');
} else {
this._timeRange.removeAttribute(Attribute.HIDDEN);
}
toggleAttribute(this._timeRange, Attribute.HIDDEN, this.streamType === 'live');
};

private readonly _onTitleSlotChange = () => {
if (this._titleSlot.assignedNodes().length > 0) {
this.setAttribute(Attribute.HAS_TITLE, '');
} else {
this.removeAttribute(Attribute.HAS_TITLE);
}
toggleAttribute(this, Attribute.HAS_TITLE, this._titleSlot.assignedNodes().length > 0);
};
}

Expand Down
19 changes: 18 additions & 1 deletion src/UIContainer.css
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@
pointer-events: none;
}

:host(:not([mobile])) [part~='menu-layer'] {
:host(:not([mobile]):not([tv])) [part~='menu-layer'] {
top: var(--theoplayer-menu-offset-top, 0);
bottom: var(--theoplayer-menu-offset-bottom, 0);
padding: var(--theoplayer-menu-layer-padding, 10px);
Expand Down Expand Up @@ -153,7 +153,16 @@
@mixin menu-fill-styles;
}

:host([tv]) [part~='menu-layer'] {
left: auto;
}

:host([tv]) [part='menu'] {
@mixin menu-fill-styles;
}

:host(:not([menu-opened])) [part~='menu-layer'],
:host([menu-opened][tv]) [part~='vertical-layer'],
:host([menu-opened][mobile]) [part~='vertical-layer'] {
display: none !important;
}
Expand Down Expand Up @@ -202,6 +211,14 @@ theoplayer-gesture-receiver {
display: none !important;
}

/*
* TV-only and TV-hidden elements
*/
:host([tv]) [part~='chrome'] ::slotted([tv-hidden]),
:host(:not([tv])) [part~='chrome'] ::slotted([tv-only]) {
display: none !important;
}

/*
* Live-only and live-hidden elements
*/
Expand Down
Loading

0 comments on commit 1905aee

Please sign in to comment.