1
0
Fork 0
mirror of https://gitlab.com/futo-org/fcast.git synced 2025-06-24 21:25:23 +00:00

Receivers: Added image viewer UI

This commit is contained in:
Michael Hollister 2025-06-18 16:58:35 -05:00
parent 023f1054e1
commit 7e4b5485ea
6 changed files with 419 additions and 122 deletions

View file

@ -22,11 +22,13 @@ export class Timer {
private delay: number; private delay: number;
private startTime: number; private startTime: number;
private remainingTime: number; private remainingTime: number;
public started: boolean;
constructor(callback: () => void, delay: number, autoStart: boolean = true) { constructor(callback: () => void, delay: number, autoStart: boolean = true) {
this.handle = null; this.handle = null;
this.callback = callback; this.callback = callback;
this.delay = delay; this.delay = delay;
this.started = false;
if (autoStart) { if (autoStart) {
this.start(); this.start();
@ -40,6 +42,7 @@ export class Timer {
window.clearTimeout(this.handle); window.clearTimeout(this.handle);
} }
this.started = true;
this.startTime = Date.now(); this.startTime = Date.now();
this.remainingTime = null; this.remainingTime = null;
this.handle = window.setTimeout(this.callback, this.delay); this.handle = window.setTimeout(this.callback, this.delay);
@ -64,6 +67,7 @@ export class Timer {
window.clearTimeout(this.handle); window.clearTimeout(this.handle);
this.handle = null; this.handle = null;
this.remainingTime = null; this.remainingTime = null;
this.started = false;
} }
} }
} }

View file

@ -1,5 +1,5 @@
import { EventMessage, EventType, KeyEvent, MediaItem, MediaItemEvent, PlaylistContent, PlayMessage, SeekMessage, SetPlaylistItemMessage, SetSpeedMessage, SetVolumeMessage } from 'common/Packets'; import { EventMessage, EventType, GenericMediaMetadata, KeyEvent, MediaItem, MediaItemEvent, MetadataType, PlaybackState, PlaylistContent, PlayMessage, SeekMessage, SetPlaylistItemMessage, SetSpeedMessage, SetVolumeMessage } from 'common/Packets';
import { mediaItemFromPlayMessage, playMessageFromMediaItem } from 'common/UtilityFrontend'; import { mediaItemFromPlayMessage, playMessageFromMediaItem, Timer } from 'common/UtilityFrontend';
import { supportedImageTypes } from 'common/MimeTypes'; import { supportedImageTypes } from 'common/MimeTypes';
import * as connectionMonitor from 'common/ConnectionMonitor'; import * as connectionMonitor from 'common/ConnectionMonitor';
import { toast, ToastIcon } from 'common/components/Toast'; import { toast, ToastIcon } from 'common/components/Toast';
@ -10,18 +10,53 @@ import {
const logger = window.targetAPI.logger; const logger = window.targetAPI.logger;
const idleBackground = document.getElementById('video-player'); // HTML elements
const idleIcon = document.getElementById('title-icon'); const idleBackground = document.getElementById('idleBackground');
// todo: add callbacks for on-load events for image and generic content viewer const idleIcon = document.getElementById('titleIcon');
const loadingSpinner = document.getElementById('loading-spinner'); const loadingSpinner = document.getElementById('loadingSpinner');
const imageViewer = document.getElementById('viewer-image') as HTMLImageElement; const imageViewer = document.getElementById('viewerImage') as HTMLImageElement;
const genericViewer = document.getElementById('viewer-generic') as HTMLIFrameElement; const genericViewer = document.getElementById('viewerGeneric') as HTMLIFrameElement;
const mediaTitle = document.getElementById("mediaTitle");
const playerControls = document.getElementById("controls");
const playerCtrlPlayPrevious = document.getElementById("playPrevious");
const playerCtrlAction = document.getElementById("action");
const playerCtrlPlaylistLength = document.getElementById("playlistLength");
const playerCtrlPlayNext = document.getElementById("playNext");
let cachedPlaylist: PlaylistContent = null; let cachedPlaylist: PlaylistContent = null;
let cachedPlayMediaItem: MediaItem = null; let cachedPlayMediaItem: MediaItem = null;
let showDurationTimeout: number = null;
let playlistIndex = 0; let playlistIndex = 0;
let isMediaItem = false; let isMediaItem = false;
let playItemCached = false; let playItemCached = false;
let imageViewerPlaybackState: PlaybackState = PlaybackState.Idle;
let uiHideTimer = new Timer(() => {
uiVisible = false;
playerCtrlStateUpdate(PlayerControlEvent.UiFadeOut);
}, 3000);
let loadingTimer = new Timer(() => { loadingSpinner.style.display = 'block'; }, 100, false);
let showDurationTimer = new Timer(() => {
if (playlistIndex < cachedPlaylist.items.length - 1) {
setPlaylistItem(playlistIndex + 1);
}
else {
logger.info('End of playlist');
imageViewer.style.display = 'none';
imageViewer.src = '';
genericViewer.style.display = 'none';
genericViewer.src = '';
idleBackground.style.display = 'block';
idleIcon.style.display = 'block';
playerCtrlAction.setAttribute("class", "play iconSize");
imageViewerPlaybackState = PlaybackState.Idle;
}
}, 0, false);
function onPlay(_event, value: PlayMessage) { function onPlay(_event, value: PlayMessage) {
if (!playItemCached) { if (!playItemCached) {
@ -31,12 +66,21 @@ function onPlay(_event, value: PlayMessage) {
window.targetAPI.sendEvent(new EventMessage(Date.now(), new MediaItemEvent(EventType.MediaItemChange, cachedPlayMediaItem))); window.targetAPI.sendEvent(new EventMessage(Date.now(), new MediaItemEvent(EventType.MediaItemChange, cachedPlayMediaItem)));
logger.info('Media playback changed:', cachedPlayMediaItem); logger.info('Media playback changed:', cachedPlayMediaItem);
playItemCached = false; playItemCached = false;
showDurationTimer.stop();
window.targetAPI.sendEvent(new EventMessage(Date.now(), new MediaItemEvent(EventType.MediaItemChange, cachedPlayMediaItem))); window.targetAPI.sendEvent(new EventMessage(Date.now(), new MediaItemEvent(EventType.MediaItemChange, cachedPlayMediaItem)));
const src = value.url ? value.url : value.content; const src = value.url ? value.url : value.content;
loadingTimer.start();
if (src && value.container && supportedImageTypes.find(v => v === value.container.toLocaleLowerCase()) && imageViewer) { if (src && value.container && supportedImageTypes.find(v => v === value.container.toLocaleLowerCase()) && imageViewer) {
logger.info('Loading image viewer'); logger.info('Loading image viewer');
imageViewer.onload = (ev) => {
loadingTimer.stop();
loadingSpinner.style.display = 'none';
playerCtrlStateUpdate(PlayerControlEvent.Load);
};
genericViewer.onload = (ev) => {};
genericViewer.style.display = 'none'; genericViewer.style.display = 'none';
genericViewer.src = ''; genericViewer.src = '';
@ -45,12 +89,21 @@ function onPlay(_event, value: PlayMessage) {
imageViewer.src = src; imageViewer.src = src;
imageViewer.style.display = 'block'; imageViewer.style.display = 'block';
playerControls.style.display = 'block';
} }
else if (src && genericViewer) { else if (src && genericViewer) {
logger.info('Loading generic viewer'); logger.info('Loading generic viewer');
imageViewer.onload = (ev) => {};
genericViewer.onload = (ev) => {
loadingTimer.stop();
loadingSpinner.style.display = 'none';
playerCtrlStateUpdate(PlayerControlEvent.Load);
};
imageViewer.style.display = 'none'; imageViewer.style.display = 'none';
imageViewer.src = ''; imageViewer.src = '';
playerControls.style.display = 'none';
idleBackground.style.display = 'none'; idleBackground.style.display = 'none';
idleIcon.style.display = 'none'; idleIcon.style.display = 'none';
@ -58,9 +111,14 @@ function onPlay(_event, value: PlayMessage) {
genericViewer.style.display = 'block'; genericViewer.style.display = 'block';
} else { } else {
logger.error('Error loading content'); logger.error('Error loading content');
loadingTimer.stop();
loadingSpinner.style.display = 'none';
imageViewer.onload = (ev) => {};
genericViewer.onload = (ev) => {};
imageViewer.style.display = 'none'; imageViewer.style.display = 'none';
imageViewer.src = ''; imageViewer.src = '';
playerControls.style.display = 'none';
genericViewer.style.display = 'none'; genericViewer.style.display = 'none';
genericViewer.src = ''; genericViewer.src = '';
@ -68,29 +126,6 @@ function onPlay(_event, value: PlayMessage) {
idleBackground.style.display = 'block'; idleBackground.style.display = 'block';
idleIcon.style.display = 'block'; idleIcon.style.display = 'block';
} }
if (isMediaItem && cachedPlayMediaItem.showDuration && cachedPlayMediaItem.showDuration > 0) {
showDurationTimeout = window.setTimeout(() => {
playlistIndex++;
if (playlistIndex < cachedPlaylist.items.length) {
cachedPlayMediaItem = cachedPlaylist.items[playlistIndex];
playItemCached = true;
window.targetAPI.sendPlayRequest(playMessageFromMediaItem(cachedPlaylist.items[playlistIndex]), playlistIndex);
}
else {
logger.info('End of playlist');
imageViewer.style.display = 'none';
imageViewer.src = '';
genericViewer.style.display = 'none';
genericViewer.src = '';
idleBackground.style.display = 'block';
idleIcon.style.display = 'block';
}
}, cachedPlayMediaItem.showDuration * 1000);
}
}; };
function onPlayPlaylist(_event, value: PlaylistContent) { function onPlayPlaylist(_event, value: PlaylistContent) {
@ -111,28 +146,38 @@ function onPlayPlaylist(_event, value: PlaylistContent) {
window.targetAPI.sendPlayRequest(playMessage, playlistIndex); window.targetAPI.sendPlayRequest(playMessage, playlistIndex);
} }
function setPlaylistItem(index: number) {
if (index === -1) {
logger.info('Looping playlist to end');
index = cachedPlaylist.items.length - 1;
}
else if (index === cachedPlaylist.items.length) {
logger.info('Looping playlist to start');
index = 0;
}
if (index >= 0 && index < cachedPlaylist.items.length) {
logger.info(`Setting playlist item to index ${index}`);
playlistIndex = index;
cachedPlayMediaItem = cachedPlaylist.items[playlistIndex];
playItemCached = true;
window.targetAPI.sendPlayRequest(playMessageFromMediaItem(cachedPlaylist.items[playlistIndex]), playlistIndex);
showDurationTimer.stop();
}
else {
logger.warn(`Playlist index out of bounds ${index}, ignoring...`);
}
playerCtrlPlaylistLength.textContent= `${playlistIndex+1} of ${cachedPlaylist.items.length}`;
}
window.targetAPI.onPause(() => { logger.warn('onPause handler invoked for generic content viewer'); }); window.targetAPI.onPause(() => { logger.warn('onPause handler invoked for generic content viewer'); });
window.targetAPI.onResume(() => { logger.warn('onResume handler invoked for generic content viewer'); }); window.targetAPI.onResume(() => { logger.warn('onResume handler invoked for generic content viewer'); });
window.targetAPI.onSeek((_event, value: SeekMessage) => { logger.warn('onSeek handler invoked for generic content viewer'); }); window.targetAPI.onSeek((_event, value: SeekMessage) => { logger.warn('onSeek handler invoked for generic content viewer'); });
window.targetAPI.onSetVolume((_event, value: SetVolumeMessage) => { logger.warn('onSetVolume handler invoked for generic content viewer'); }); window.targetAPI.onSetVolume((_event, value: SetVolumeMessage) => { logger.warn('onSetVolume handler invoked for generic content viewer'); });
window.targetAPI.onSetSpeed((_event, value: SetSpeedMessage) => { logger.warn('onSetSpeed handler invoked for generic content viewer'); }); window.targetAPI.onSetSpeed((_event, value: SetSpeedMessage) => { logger.warn('onSetSpeed handler invoked for generic content viewer'); });
window.targetAPI.onSetPlaylistItem((_event, value: SetPlaylistItemMessage) => { window.targetAPI.onSetPlaylistItem((_event, value: SetPlaylistItemMessage) => { setPlaylistItem(value.itemIndex); });
if (value.itemIndex >= 0 && value.itemIndex < cachedPlaylist.items.length) {
logger.info(`Setting playlist item to index ${value.itemIndex}`);
playlistIndex = value.itemIndex;
cachedPlayMediaItem = cachedPlaylist.items[playlistIndex];
playItemCached = true;
window.targetAPI.sendPlayRequest(playMessageFromMediaItem(cachedPlaylist.items[playlistIndex]), playlistIndex);
if (showDurationTimeout) {
window.clearTimeout(showDurationTimeout);
showDurationTimeout = null;
}
}
else {
logger.warn(`Playlist index out of bounds ${value.itemIndex}, ignoring...`);
}
});
connectionMonitor.setUiUpdateCallbacks({ connectionMonitor.setUiUpdateCallbacks({
onConnect: (connections: string[], initialUpdate: boolean = false) => { onConnect: (connections: string[], initialUpdate: boolean = false) => {
@ -152,13 +197,8 @@ enum PlayerControlEvent {
Load, Load,
Pause, Pause,
Play, Play,
VolumeChange,
TimeUpdate,
UiFadeOut, UiFadeOut,
UiFadeIn, UiFadeIn,
SetCaptions,
ToggleSpeedMenu,
SetPlaybackRate,
ToggleFullscreen, ToggleFullscreen,
ExitFullscreen, ExitFullscreen,
} }
@ -172,14 +212,77 @@ function playerCtrlStateUpdate(event: PlayerControlEvent) {
switch (event) { switch (event) {
case PlayerControlEvent.Load: { case PlayerControlEvent.Load: {
if (isMediaItem) {
playerCtrlPlayPrevious.style.display = 'block';
playerCtrlPlayNext.style.display = 'block';
if (cachedPlayMediaItem.showDuration && cachedPlayMediaItem.showDuration > 0) {
playerCtrlAction.style.display = 'block';
playerCtrlPlaylistLength.style.display = 'none';
if (imageViewerPlaybackState === PlaybackState.Idle || imageViewerPlaybackState === PlaybackState.Playing) {
showDurationTimer.start(cachedPlayMediaItem.showDuration * 1000);
playerCtrlAction.setAttribute("class", "pause iconSize");
imageViewerPlaybackState = PlaybackState.Playing;
}
}
else {
playerCtrlAction.style.display = 'none';
playerCtrlPlaylistLength.textContent= `${playlistIndex+1} of ${cachedPlaylist.items.length}`;
playerCtrlPlaylistLength.style.display = 'block';
}
}
else {
playerCtrlPlayPrevious.style.display = 'none';
playerCtrlPlayNext.style.display = 'none';
playerCtrlAction.style.display = 'none';
playerCtrlPlaylistLength.style.display = 'none';
}
if (cachedPlayMediaItem.metadata && cachedPlayMediaItem.metadata?.type === MetadataType.Generic) {
const metadata = cachedPlayMediaItem.metadata as GenericMediaMetadata;
if (metadata.title) {
mediaTitle.innerHTML = metadata.title;
}
}
break; break;
} }
case PlayerControlEvent.Pause:
playerCtrlAction.setAttribute("class", "play iconSize");
imageViewerPlaybackState = PlaybackState.Paused;
showDurationTimer.pause();
break;
case PlayerControlEvent.Play:
playerCtrlAction.setAttribute("class", "pause iconSize");
if (imageViewerPlaybackState === PlaybackState.Idle) {
setPlaylistItem(0);
}
else {
if (showDurationTimer.started) {
showDurationTimer.resume();
}
else {
showDurationTimer.start(cachedPlayMediaItem.showDuration * 1000);
}
imageViewerPlaybackState = PlaybackState.Playing;
}
break;
case PlayerControlEvent.UiFadeOut: { case PlayerControlEvent.UiFadeOut: {
document.body.style.cursor = "none";
playerControls.style.opacity = '0';
break; break;
} }
case PlayerControlEvent.UiFadeIn: { case PlayerControlEvent.UiFadeIn: {
document.body.style.cursor = "default";
playerControls.style.opacity = '1';
break; break;
} }
@ -188,61 +291,79 @@ function playerCtrlStateUpdate(event: PlayerControlEvent) {
} }
} }
document.addEventListener('keydown', (event: KeyboardEvent) => { // Receiver generated event handlers
playerCtrlAction.onclick = () => {
if (imageViewerPlaybackState === PlaybackState.Paused || imageViewerPlaybackState === PlaybackState.Idle) {
playerCtrlStateUpdate(PlayerControlEvent.Play);
} else {
playerCtrlStateUpdate(PlayerControlEvent.Pause);
}
};
playerCtrlPlayPrevious.onclick = () => { setPlaylistItem(playlistIndex - 1); }
playerCtrlPlayNext.onclick = () => { setPlaylistItem(playlistIndex + 1); }
// Component hiding
let uiVisible = true;
function stopUiHideTimer() {
uiHideTimer.stop();
if (!uiVisible) {
uiVisible = true;
playerCtrlStateUpdate(PlayerControlEvent.UiFadeIn);
}
}
document.onmouseout = () => {
uiHideTimer.stop();
uiVisible = false;
playerCtrlStateUpdate(PlayerControlEvent.UiFadeOut);
}
document.onmousemove = () => {
stopUiHideTimer();
uiHideTimer.start();
};
function keyDownEventListener(event: KeyboardEvent) {
// logger.info("KeyDown", event); // logger.info("KeyDown", event);
let handledCase = targetKeyDownEventListener(event); let handledCase = targetKeyDownEventListener(event);
if (!handledCase) { if (!handledCase) {
switch (event.code) { switch (event.code) {
case 'ArrowLeft': { case 'ArrowLeft':
// skipBack(); setPlaylistItem(playlistIndex - 1);
// event.preventDefault(); event.preventDefault();
// handledCase = true; handledCase = true;
// const value = { itemIndex: playlistIndex - 1 };
// if (value.itemIndex >= 0 && value.itemIndex < cachedPlaylist.items.length) {
// logger.info(`Setting playlist item to index ${value.itemIndex}`);
// playlistIndex = value.itemIndex;
// cachedPlayMediaItem = cachedPlaylist.items[playlistIndex];
// playItemCached = true;
// window.targetAPI.sendPlayRequest(playMessageFromMediaItem(cachedPlaylist.items[playlistIndex]), playlistIndex);
// if (showDurationTimeout) {
// window.clearTimeout(showDurationTimeout);
// showDurationTimeout = null;
// }
// }
// else {
// logger.warn(`Playlist index out of bounds ${value.itemIndex}, ignoring...`);
// }
break; break;
} case 'ArrowRight':
case 'ArrowRight': { setPlaylistItem(playlistIndex + 1);
// skipForward(); event.preventDefault();
// event.preventDefault(); handledCase = true;
// handledCase = true; break;
case "Home":
// const value = { itemIndex: playlistIndex + 1 }; setPlaylistItem(0);
// if (value.itemIndex >= 0 && value.itemIndex < cachedPlaylist.items.length) { event.preventDefault();
// logger.info(`Setting playlist item to index ${value.itemIndex}`); handledCase = true;
// playlistIndex = value.itemIndex; break;
// cachedPlayMediaItem = cachedPlaylist.items[playlistIndex]; case "End":
// playItemCached = true; setPlaylistItem(cachedPlaylist.items.length - 1);
// window.targetAPI.sendPlayRequest(playMessageFromMediaItem(cachedPlaylist.items[playlistIndex]), playlistIndex); event.preventDefault();
handledCase = true;
// if (showDurationTimeout) { break;
// window.clearTimeout(showDurationTimeout); case 'KeyK':
// showDurationTimeout = null; case 'Space':
// } case 'Enter':
// } // Play/pause toggle
// else { if (imageViewerPlaybackState === PlaybackState.Paused || imageViewerPlaybackState === PlaybackState.Idle) {
// logger.warn(`Playlist index out of bounds ${value.itemIndex}, ignoring...`); playerCtrlStateUpdate(PlayerControlEvent.Play);
// } } else {
playerCtrlStateUpdate(PlayerControlEvent.Pause);
}
event.preventDefault();
handledCase = true;
break; break;
}
default: default:
break; break;
} }
@ -251,7 +372,9 @@ document.addEventListener('keydown', (event: KeyboardEvent) => {
if (window.targetAPI.getSubscribedKeys().keyDown.has(event.key)) { if (window.targetAPI.getSubscribedKeys().keyDown.has(event.key)) {
window.targetAPI.sendEvent(new EventMessage(Date.now(), new KeyEvent(EventType.KeyDown, event.key, event.repeat, handledCase))); window.targetAPI.sendEvent(new EventMessage(Date.now(), new KeyEvent(EventType.KeyDown, event.key, event.repeat, handledCase)));
} }
}); }
document.addEventListener('keydown', keyDownEventListener);
document.addEventListener('keyup', (event: KeyboardEvent) => { document.addEventListener('keyup', (event: KeyboardEvent) => {
if (window.targetAPI.getSubscribedKeys().keyUp.has(event.key)) { if (window.targetAPI.getSubscribedKeys().keyUp.has(event.key)) {
window.targetAPI.sendEvent(new EventMessage(Date.now(), new KeyEvent(EventType.KeyUp, event.key, event.repeat, false))); window.targetAPI.sendEvent(new EventMessage(Date.now(), new KeyEvent(EventType.KeyUp, event.key, event.repeat, false)));
@ -260,6 +383,10 @@ document.addEventListener('keyup', (event: KeyboardEvent) => {
export { export {
PlayerControlEvent, PlayerControlEvent,
idleBackground,
idleIcon,
imageViewer,
genericViewer,
onPlay, onPlay,
playerCtrlStateUpdate, playerCtrlStateUpdate,
}; };

View file

@ -32,7 +32,7 @@ body {
height: 100%; height: 100%;
} }
#title-icon { #titleIcon {
position: absolute; position: absolute;
left: 50%; left: 50%;
top: 50%; top: 50%;
@ -42,6 +42,122 @@ body {
background-size: cover; background-size: cover;
} }
.container {
position: absolute;
bottom: 0px;
/* height: 100%; */
height: 120px;
width: 100%;
/* background: linear-gradient(to top, rgba(0, 0, 0, 0.8) 0%, rgba(0, 0, 0, 0) 100%); */
background: linear-gradient(to top, rgba(0, 0, 0, 0.8) 0%, rgba(0, 0, 0, 0.0) 35%);
background-size: 100% 300px;
background-repeat: no-repeat;
background-position: bottom;
opacity: 1;
transition: opacity 0.1s ease-in-out;
}
.iconSize {
width: 24px;
height: 24px;
}
.buttonContainer {
position: absolute;
bottom: 24px;
height: 24px;
/* width: calc(50% - 24px); */
align-items: center;
overflow: hidden;
display: flex;
gap: 24px;
user-select: none;
}
#leftButtonContainer {
left: 24px;
right: 60%;
flex-direction: row;
font-family: InterVariable;
font-size: 24px;
font-style: normal;
font-weight: 400;
}
#centerButtonContainer {
left: 50%;
transform: translate(-50%, 0%);
font-family: InterVariable;
font-size: 24px;
font-style: normal;
font-weight: 400;
}
#rightButtonContainer {
right: 24px;
flex-direction: row-reverse;
}
#mediaTitle {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.play {
cursor: pointer;
flex-shrink: 0;
background-image: url("../assets/icons/player/icon24_play.svg");
transition: background-image 0.1s ease-in-out;
}
.play:hover {
background-image: url("../assets/icons/player/icon24_play_active.svg");
}
.pause {
cursor: pointer;
flex-shrink: 0;
background-image: url("../assets/icons/player/icon24_pause.svg");
transition: background-image 0.1s ease-in-out;
}
.pause:hover {
background-image: url("../assets/icons/player/icon24_pause_active.svg");
}
.playPrevious {
cursor: pointer;
flex-shrink: 0;
background-image: url("../assets/icons/player/icon24_play_previous.svg");
transition: background-image 0.1s ease-in-out;
}
.playPrevious:hover {
background-image: url("../assets/icons/player/icon24_play_previous_active.svg");
}
.playNext {
cursor: pointer;
flex-shrink: 0;
background-image: url("../assets/icons/player/icon24_play_next.svg");
transition: background-image 0.1s ease-in-out;
}
.playNext:hover {
background-image: url("../assets/icons/player/icon24_play_next_active.svg");
}
.lds-ring { .lds-ring {
display: block; display: block;
position: absolute; position: absolute;
@ -148,7 +264,7 @@ body {
/* Display scaling (Minimum supported resolution is 960x540) */ /* Display scaling (Minimum supported resolution is 960x540) */
@media only screen and ((min-width: 2560px) or (min-height: 1440px)) { @media only screen and ((min-width: 2560px) or (min-height: 1440px)) {
#title-icon { #titleIcon {
width: 164px; width: 164px;
height: 164px; height: 164px;
} }
@ -179,7 +295,7 @@ body {
} }
@media only screen and ((max-width: 2559px) or (max-height: 1439px)) { @media only screen and ((max-width: 2559px) or (max-height: 1439px)) {
#title-icon { #titleIcon {
width: 124px; width: 124px;
height: 124px; height: 124px;
} }
@ -210,7 +326,7 @@ body {
} }
@media only screen and ((max-width: 1919px) or (max-height: 1079px)) { @media only screen and ((max-width: 1919px) or (max-height: 1079px)) {
#title-icon { #titleIcon {
width: 84px; width: 84px;
height: 84px; height: 84px;
} }
@ -241,7 +357,7 @@ body {
} }
@media only screen and ((max-width: 1279px) or (max-height: 719px)) { @media only screen and ((max-width: 1279px) or (max-height: 719px)) {
#title-icon { #titleIcon {
width: 64px; width: 64px;
height: 64px; height: 64px;
} }

View file

@ -1,4 +1,11 @@
import { PlayerControlEvent, playerCtrlStateUpdate } from 'common/viewer/Renderer'; import { PlayerControlEvent, playerCtrlStateUpdate, idleBackground, idleIcon, imageViewer, genericViewer } from 'common/viewer/Renderer';
const playerCtrlFullscreen = document.getElementById("fullscreen");
playerCtrlFullscreen.onclick = () => { playerCtrlStateUpdate(PlayerControlEvent.ToggleFullscreen); };
idleBackground.ondblclick = () => { playerCtrlStateUpdate(PlayerControlEvent.ToggleFullscreen); };
idleIcon.ondblclick = () => { playerCtrlStateUpdate(PlayerControlEvent.ToggleFullscreen); };
imageViewer.ondblclick = () => { playerCtrlStateUpdate(PlayerControlEvent.ToggleFullscreen); };
genericViewer.ondblclick = () => { playerCtrlStateUpdate(PlayerControlEvent.ToggleFullscreen); };
export function targetPlayerCtrlStateUpdate(event: PlayerControlEvent): boolean { export function targetPlayerCtrlStateUpdate(event: PlayerControlEvent): boolean {
let handledCase = false; let handledCase = false;
@ -7,13 +14,13 @@ export function targetPlayerCtrlStateUpdate(event: PlayerControlEvent): boolean
case PlayerControlEvent.ToggleFullscreen: { case PlayerControlEvent.ToggleFullscreen: {
window.electronAPI.toggleFullScreen(); window.electronAPI.toggleFullScreen();
// window.electronAPI.isFullScreen().then((isFullScreen: boolean) => { window.electronAPI.isFullScreen().then((isFullScreen: boolean) => {
// if (isFullScreen) { if (isFullScreen) {
// playerCtrlFullscreen.setAttribute("class", "fullscreen_on"); playerCtrlFullscreen.setAttribute("class", "fullscreen_on");
// } else { } else {
// playerCtrlFullscreen.setAttribute("class", "fullscreen_off"); playerCtrlFullscreen.setAttribute("class", "fullscreen_off");
// } }
// }); });
handledCase = true; handledCase = true;
break; break;
@ -21,7 +28,7 @@ export function targetPlayerCtrlStateUpdate(event: PlayerControlEvent): boolean
case PlayerControlEvent.ExitFullscreen: case PlayerControlEvent.ExitFullscreen:
window.electronAPI.exitFullScreen(); window.electronAPI.exitFullScreen();
// playerCtrlFullscreen.setAttribute("class", "fullscreen_off"); playerCtrlFullscreen.setAttribute("class", "fullscreen_off");
handledCase = true; handledCase = true;
break; break;

View file

@ -6,16 +6,33 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="../assets/fonts/inter.css" /> <link rel="stylesheet" href="../assets/fonts/inter.css" />
<link rel="stylesheet" href="./common.css" /> <link rel="stylesheet" href="./common.css" />
<link rel="stylesheet" href="./style.css" />
</head> </head>
<body> <body>
<!-- Empty video element as a workaround to fix issue with white border outline without it... --> <!-- Empty video element as a workaround to fix issue with white border outline without it... -->
<video id="video-player" class="video"></video> <video id="idleBackground" class="video"></video>
<div id="viewer" class="viewer"> <div id="viewer" class="viewer">
<div id="title-icon"></div> <div id="titleIcon"></div>
<img id="viewer-image" class="viewer" /> <div id="loadingSpinner" class="lds-ring"><div></div><div></div><div></div><div></div></div>
<iframe id="viewer-generic" class="viewer"></iframe> <img id="viewerImage" class="viewer" />
<iframe id="viewerGeneric" class="viewer"></iframe>
</div>> </div>>
<div id="controls" class="container">
<div id="leftButtonContainer" class="buttonContainer">
<div id="mediaTitle"></div>
</div>
<div id="centerButtonContainer" class="buttonContainer">
<div id="playPrevious" class="playPrevious iconSize" style="display: none"></div>
<div id="action" class="play iconSize" style="display: none"></div>
<div id="playlistLength" style="display: none"></div>
<div id="playNext" class="playNext iconSize" style="display: none"></div>
</div>
<div id="rightButtonContainer" class="buttonContainer">
<div id="fullscreen" class="fullscreen_on iconSize"></div>
</div>
</div>
<div id="toast-notification"> <div id="toast-notification">
<div id="toast-icon"></div> <div id="toast-icon"></div>
@ -24,4 +41,4 @@
<script src="./renderer.js"></script> <script src="./renderer.js"></script>
</body> </body>
</html> </html>

View file

@ -0,0 +1,26 @@
.fullscreen_on {
width: 24px;
height: 24px;
cursor: pointer;
background-image: url("../assets/icons/player/icon24_fullscreen_on.svg");
transition: background-image 0.1s ease-in-out;
}
.fullscreen_on:hover {
background-image: url("../assets/icons/player/icon24_fullscreen_on_active.svg");
}
.fullscreen_off {
width: 24px;
height: 24px;
cursor: pointer;
background-image: url("../assets/icons/player/icon24_fullscreen_off.svg");
transition: background-image 0.1s ease-in-out;
}
.fullscreen_off:hover {
background-image: url("../assets/icons/player/icon24_fullscreen_off_active.svg");
}