Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "hdtilt",
"version": "0.4.0",
"version": "0.4.1",
"description": "Open-source IPTV player: M3U, Xtream Codes and XMLTV, with a TV guide. Web/PWA, desktop, CLI, TUI, MCP and API.",
"license": "MIT",
"author": {
Expand Down
2 changes: 1 addition & 1 deletion src/version.js
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const VERSION = '0.4.0';
export const VERSION = '0.4.1';
29 changes: 29 additions & 0 deletions web/src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,23 @@ const esc = (s) =>
const hhmm = (t) => new Date(t).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
const isTv = isTvBrowser(navigator.userAgent);

// On a TV the picture belongs on the whole screen, and reaching a small ⛶
// button with a remote is a chore. Browsers only allow fullscreen from a
// touch or key press, so the first one does it, and so does picking a
// channel or film. Leaving fullscreen is honoured until the next pick. On by
// default on a TV browser; ?fullscreen=on|off overrides that, and is remembered.
{
const asked = new URLSearchParams(location.search).get('fullscreen');
if (asked === 'on' || asked === 'off') store.prefs.write('fullscreen', asked);
}
const fsAuto = store.prefs.read('fullscreen', isTv ? 'on' : 'off') === 'on';
let fsDeclined = false;
function autoFullscreen() {
if (!fsAuto || fsDeclined || document.fullscreenElement || !document.fullscreenEnabled) return;
if (navigator.userActivation && !navigator.userActivation.isActive) return;
document.documentElement.requestFullscreen?.().catch(() => {});
}

const FAV = '★ Favorites';
const RECENT = '↺ Recent';
const ALL = 'All channels';
Expand Down Expand Up @@ -253,6 +270,8 @@ function setView() {
async function play(ch, { viaProxy = false, url = null, programme = null } = {}) {
if (!ch) return;
if (ch.kind && ch.kind !== 'live') return playVod(ch);
fsDeclined = false;
autoFullscreen();
if (S.vod) {
saveVodPos();
S.vod = null;
Expand Down Expand Up @@ -335,6 +354,9 @@ const PLAIN_VIDEO = new Set(['h264', 'vp9', 'av1', 'vp8']);
async function playVod(item, { start = null, remuxed = false, show = undefined } = {}) {
if (!item) return;
if (S.vod && S.vod.item.id !== item.id) saveVodPos();
// A seek restarts the same title: leaving fullscreen is still honoured then.
if (S.vod?.item.id !== item.id) fsDeclined = false;
autoFullscreen();
if (S.playing && S.playing.kind === 'live') S.previous = S.playing;
show = show === undefined ? (S.vod?.item.id === item.id ? S.vod.show : vodShowFor(item)) : show;
S.playing = item;
Expand Down Expand Up @@ -762,6 +784,13 @@ function soundOn(e) {
}
for (const ev of ['pointerdown', 'keydown']) addEventListener(ev, soundOn, true);

addEventListener('keydown', (e) => e.key !== 'Escape' && autoFullscreen(), true);
addEventListener('pointerdown', autoFullscreen, true);
// Back or Esc on a remote leaves fullscreen without asking us: that counts as a no.
document.addEventListener('fullscreenchange', () => {
if (!document.fullscreenElement) fsDeclined = true;
});

function hideChrome() {
// The subtitles panel sits on the banner: it stays up until it is closed.
if (subs.isOpen()) return;
Expand Down
Loading