1
0
Fork 0
mirror of https://gitlab.com/futo-org/fcast.git synced 2025-08-03 15:57:01 +00:00

Receivers: Add playlist next/previous buttons and metadata for audio files

This commit is contained in:
Michael Hollister 2025-06-16 15:17:16 -05:00
parent 782a01f4e9
commit 56e10760b8
8 changed files with 151 additions and 32 deletions

View file

@ -1,6 +1,6 @@
import dashjs from 'modules/dashjs';
import Hls, { LevelLoadedData } from 'modules/hls.js';
import { EventMessage, EventType, KeyEvent, MediaItem, MediaItemEvent, PlaybackState, PlaybackUpdateMessage, PlaylistContent, PlayMessage, SeekMessage, SetPlaylistItemMessage, SetSpeedMessage, SetVolumeMessage } from 'common/Packets';
import { EventMessage, EventType, GenericMediaMetadata, KeyEvent, MediaItem, MediaItemEvent, MetadataType, PlaybackState, PlaybackUpdateMessage, PlaylistContent, PlayMessage, SeekMessage, SetPlaylistItemMessage, SetSpeedMessage, SetVolumeMessage } from 'common/Packets';
import { Player, PlayerType } from './Player';
import * as connectionMonitor from 'common/ConnectionMonitor';
import { supportedAudioTypes } from 'common/MimeTypes';
@ -88,12 +88,16 @@ function onPlayerLoad(value: PlayMessage) {
const idleIcon = document.getElementById('title-icon');
const loadingSpinner = document.getElementById('loading-spinner');
const idleBackground = document.getElementById('idle-background');
const thumbnailImage = document.getElementById('thumbnailImage') as HTMLImageElement;
const videoElement = document.getElementById("videoPlayer") as HTMLVideoElement;
const videoCaptions = document.getElementById("videoCaptions") as HTMLDivElement;
const mediaTitle = document.getElementById("mediaTitle");
const playerControls = document.getElementById("controls");
const playerCtrlPlayPrevious = document.getElementById("playPrevious");
const playerCtrlAction = document.getElementById("action");
const playerCtrlPlayNext = document.getElementById("playNext");
const playerCtrlVolume = document.getElementById("volume");
const playerCtrlProgressBar = document.getElementById("progressBar");
@ -141,6 +145,7 @@ let showDurationTimeout: number = null;
let playlistIndex = 0;
let isMediaItem = false;
let playItemCached = false;
let mediaTitleTimeoutHandle = null;
function onPlay(_event, value: PlayMessage) {
if (!playItemCached) {
@ -372,10 +377,10 @@ function onPlayPlaylist(_event, value: PlaylistContent) {
window.targetAPI.sendPlayRequest(playMessage, playlistIndex);
}
window.targetAPI.onSetPlaylistItem((_event, value: SetPlaylistItemMessage) => {
if (value.itemIndex >= 0 && value.itemIndex < cachedPlaylist.items.length) {
logger.info(`Setting playlist item to index ${value.itemIndex}`);
playlistIndex = value.itemIndex;
function setPlaylistItem(index: number) {
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);
@ -386,9 +391,9 @@ window.targetAPI.onSetPlaylistItem((_event, value: SetPlaylistItemMessage) => {
}
}
else {
logger.warn(`Playlist index out of bounds ${value.itemIndex}, ignoring...`);
logger.warn(`Playlist index out of bounds ${index}, ignoring...`);
}
});
}
connectionMonitor.setUiUpdateCallbacks({
onConnect: (connections: string[], initialUpdate: boolean = false) => {
@ -403,6 +408,7 @@ connectionMonitor.setUiUpdateCallbacks({
window.targetAPI.onPlay(onPlay);
window.targetAPI.onPlayPlaylist(onPlayPlaylist);
window.targetAPI.onSetPlaylistItem((_event, value: SetPlaylistItemMessage) => { setPlaylistItem(value.itemIndex); });
let scrubbing = false;
let volumeChanging = false;
@ -431,6 +437,15 @@ function playerCtrlStateUpdate(event: PlayerControlEvent) {
switch (event) {
case PlayerControlEvent.Load: {
if (isMediaItem) {
playerCtrlPlayPrevious.style.display = 'block';
playerCtrlPlayNext.style.display = 'block';
}
else {
playerCtrlPlayPrevious.style.display = 'none';
playerCtrlPlayNext.style.display = 'none';
}
playerCtrlProgressBarBuffer.setAttribute("style", "width: 0px");
playerCtrlProgressBarProgress.setAttribute("style", "width: 0px");
playerCtrlProgressBarHandle.setAttribute("style", `left: ${playerCtrlProgressBar.offsetLeft}px`);
@ -440,30 +455,54 @@ function playerCtrlStateUpdate(event: PlayerControlEvent) {
playerCtrlVolumeBarHandle.setAttribute("style", `left: ${volume + 8}px`);
if (isLive) {
playerCtrlLiveBadge.setAttribute("style", "display: block");
playerCtrlPosition.setAttribute("style", "display: none");
playerCtrlDurationSeparator.setAttribute("style", "display: none");
playerCtrlDuration.setAttribute("style", "display: none");
playerCtrlLiveBadge.style.display = 'block';
playerCtrlPosition.style.display = 'none';
playerCtrlDurationSeparator.style.display = 'none';
playerCtrlDuration.style.display = 'none';
}
else {
playerCtrlLiveBadge.setAttribute("style", "display: none");
playerCtrlPosition.setAttribute("style", "display: block");
playerCtrlDurationSeparator.setAttribute("style", "display: block");
playerCtrlDuration.setAttribute("style", "display: block");
playerCtrlLiveBadge.style.display = 'none';
playerCtrlPosition.style.display = 'block';
playerCtrlDurationSeparator.style.display = 'block';
playerCtrlDuration.style.display = 'block';
playerCtrlPosition.textContent = formatDuration(player.getCurrentTime());
playerCtrlDuration.innerHTML = formatDuration(player.getDuration());
}
if (player.isCaptionsSupported()) {
playerCtrlCaptions.setAttribute("style", "display: block");
videoCaptions.setAttribute("style", "display: block");
playerCtrlCaptions.style.display = 'block';
videoCaptions.style.display = 'block';
}
else {
playerCtrlCaptions.setAttribute("style", "display: none");
videoCaptions.setAttribute("style", "display: none");
playerCtrlCaptions.style.display = 'none';
videoCaptions.style.display = 'none';
player.enableCaptions(false);
}
playerCtrlStateUpdate(PlayerControlEvent.SetCaptions);
if (supportedAudioTypes.find(v => v === cachedPlayMediaItem.container.toLocaleLowerCase())) {
if (cachedPlayMediaItem.metadata && cachedPlayMediaItem.metadata?.type === MetadataType.Generic) {
const metadata = cachedPlayMediaItem.metadata as GenericMediaMetadata;
if (metadata.title) {
mediaTitle.innerHTML = metadata.title;
captionsContentHeight = mediaTitle.getBoundingClientRect().height - captionsLineHeight;
const captionsHeight = captionsBaseHeightExpanded + captionsContentHeight;
mediaTitle.setAttribute("style", `display: block; bottom: ${captionsHeight}px;`);
if (mediaTitleTimeoutHandle) {
clearTimeout(mediaTitleTimeoutHandle);
}
mediaTitleTimeoutHandle = setTimeout(() => {
mediaTitle.style.display = 'none';
}, 5000);
}
}
}
break;
}
@ -533,7 +572,7 @@ function playerCtrlStateUpdate(event: PlayerControlEvent) {
case PlayerControlEvent.UiFadeOut: {
document.body.style.cursor = "none";
playerControls.setAttribute("style", "opacity: 0");
playerControls.style.opacity = '0';
captionsBaseHeight = captionsBaseHeightCollapsed;
const captionsHeight = captionsBaseHeight + captionsContentHeight;
@ -543,13 +582,12 @@ function playerCtrlStateUpdate(event: PlayerControlEvent) {
videoCaptions.setAttribute("style", `display: none; bottom: ${captionsHeight}px;`);
}
break;
}
case PlayerControlEvent.UiFadeIn: {
document.body.style.cursor = "default";
playerControls.setAttribute("style", "opacity: 1");
playerControls.style.opacity = '1';
captionsBaseHeight = captionsBaseHeightExpanded;
const captionsHeight = captionsBaseHeight + captionsContentHeight;
@ -565,19 +603,19 @@ function playerCtrlStateUpdate(event: PlayerControlEvent) {
case PlayerControlEvent.SetCaptions:
if (player?.isCaptionsEnabled()) {
playerCtrlCaptions.setAttribute("class", "captions_on iconSize");
videoCaptions.setAttribute("style", "display: block");
videoCaptions.style.display = 'block';
} else {
playerCtrlCaptions.setAttribute("class", "captions_off iconSize");
videoCaptions.setAttribute("style", "display: none");
videoCaptions.style.display = 'none';
}
break;
case PlayerControlEvent.ToggleSpeedMenu: {
if (playerCtrlSpeedMenuShown) {
playerCtrlSpeedMenu.setAttribute("style", "display: none");
playerCtrlSpeedMenu.style.display = 'none';
} else {
playerCtrlSpeedMenu.setAttribute("style", "display: block");
playerCtrlSpeedMenu.style.display = 'block';
}
playerCtrlSpeedMenuShown = !playerCtrlSpeedMenuShown;
@ -590,12 +628,12 @@ function playerCtrlStateUpdate(event: PlayerControlEvent) {
playbackRates.forEach(r => {
const entry = document.getElementById(`speedMenuEntry_${r}_enabled`);
entry.setAttribute("style", "opacity: 0");
entry.style.opacity = '0';
});
// Ignore updating GUI for custom rates
if (entryElement !== null) {
entryElement.setAttribute("style", "opacity: 1");
entryElement.style.opacity = '1';
}
break;
@ -634,6 +672,8 @@ playerCtrlAction.onclick = () => {
}
};
playerCtrlPlayPrevious.onclick = () => { setPlaylistItem(playlistIndex - 1); }
playerCtrlPlayNext.onclick = () => { setPlaylistItem(playlistIndex + 1); }
playerCtrlVolume.onclick = () => { player?.setMute(!player?.isMuted()); };
PlayerCtrlProgressBarInteractiveArea.onmousedown = (e: MouseEvent) => { scrubbing = true; scrubbingMouseHandler(e) };
@ -738,11 +778,13 @@ function videoClickedHandler() {
videoElement.onclick = () => { videoClickedHandler(); };
idleBackground.onclick = () => { videoClickedHandler(); };
thumbnailImage.onclick = () => { videoClickedHandler(); };
idleIcon.onclick = () => { videoClickedHandler(); };
function setIdleScreenVisible(visible: boolean, loading: boolean = false, message?: PlayMessage) {
if (visible) {
idleBackground.style.display = 'block';
thumbnailImage.style.display = 'none';
if (loading) {
idleIcon.style.display = 'none';
@ -757,11 +799,26 @@ function setIdleScreenVisible(visible: boolean, loading: boolean = false, messag
if (!supportedAudioTypes.find(v => v === message.container.toLocaleLowerCase())) {
idleIcon.style.display = 'none';
idleBackground.style.display = 'none';
thumbnailImage.style.display = 'none';
}
else {
idleIcon.style.display = 'block';
idleBackground.style.display = 'block';
let displayThumbnail = false;
if (message?.metadata?.type === MetadataType.Generic) {
const metadata = message.metadata as GenericMediaMetadata;
displayThumbnail = metadata.thumbnailUrl ? true : false;
thumbnailImage.src = metadata.thumbnailUrl;
}
if (displayThumbnail) {
idleIcon.style.display = 'none';
idleBackground.style.display = 'none';
thumbnailImage.style.display = 'block';
}
else {
idleIcon.style.display = 'block';
idleBackground.style.display = 'block';
thumbnailImage.style.display = 'none';
}
}
loadingSpinner.style.display = 'none';

View file

@ -66,6 +66,17 @@ body {
background-color: black;
}
#thumbnailImage {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
object-fit: contain;
width: 100%;
height: 100%;
}
#videoPlayer {
object-fit: contain;
width: 100%;
@ -331,6 +342,30 @@ body {
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");
}
.volume_high {
cursor: pointer;
flex-shrink: 0;