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

Finished initial WebOS receiver implementation

This commit is contained in:
Michael Hollister 2024-12-17 00:10:12 -06:00
parent 41f80880e4
commit 2df64dca89
20 changed files with 457 additions and 92 deletions

View file

@ -1 +1,6 @@
import 'common/main/Preload';
// Cannot go back to a state where user was previously casting a video, so exit.
window.onpopstate = () => {
window.webOS.platformBack();
};

View file

@ -1 +1,22 @@
import 'common/main/Renderer';
const backgroundVideo = document.getElementById('video-player');
const loadingScreen = document.getElementById('loading-screen');
let backgroundVideoLoaded = false;
let qrCodeRendered = false;
backgroundVideo.onplaying = () => {
backgroundVideoLoaded = true;
if (backgroundVideoLoaded && qrCodeRendered) {
loadingScreen.style.display = 'none';
}
};
export function onQRCodeRendered() {
qrCodeRendered = true;
if (backgroundVideoLoaded && qrCodeRendered) {
loadingScreen.style.display = 'none';
}
}

View file

@ -12,6 +12,10 @@
</head>
<body>
<div id="loading-screen">
<div id="loading-text" class="non-selectable">Loading FCast</div>
<div id="spinner" class="lds-ring"><div></div><div></div><div></div><div></div></div>
</div>
<div id="main-container">
<video id="video-player" class="video" autoplay loop>
<source src="../assets/video/background.mp4" type="video/mp4">
@ -42,7 +46,7 @@
</div>
</div>
<div id="window-can-be-closed" class="non-selectable">App will continue to run in the background when app is closed</div>
<div id="window-can-be-closed" class="non-selectable">App will continue to listen for connections when suspended in the background</div>
</div>
</div>
<script src="./renderer.js"></script>

View file

@ -4,10 +4,17 @@
#overlay {
font-family: InterRegular;
font-size: 28px;
}
#title-text {
font-family: OutfitExtraBold;
font-size: 140px;
}
#title-icon {
width: 124px;
height: 124px;
}
#manual-connection-info {
@ -20,4 +27,33 @@
#window-can-be-closed {
font-family: InterRegular;
font-size: 24px;
}
.lds-ring {
width: 120px;
height: 120px;
}
.lds-ring div {
width: 104px;
height: 104px;
}
#loading-screen {
height: 100%;
width: 100%;
object-fit: cover;
background-color: black;
display: flex;
justify-content: center;
align-items: center;
}
#loading-text {
font-size: 100px;
font-weight: 800;
text-align: center;
color: white;
padding-right: 20px;
}

View file

@ -1,10 +1,76 @@
import { onPlay, PlayerControlEvent } from 'common/player/Renderer';
import {
isLive,
onPlay,
player,
PlayerControlEvent,
playerCtrlCaptions,
playerCtrlDuration,
playerCtrlLiveBadge,
playerCtrlPosition,
playerCtrlProgressBar,
playerCtrlProgressBarBuffer,
playerCtrlProgressBarHandle,
playerCtrlProgressBarProgress,
playerCtrlStateUpdate,
playerCtrlVolumeBar,
playerCtrlVolumeBarHandle,
playerCtrlVolumeBarProgress,
videoCaptions,
formatDuration,
} from 'common/player/Renderer';
const captionsBaseHeightCollapsed = 150;
const captionsBaseHeightExpanded = 320;
const captionsLineHeight = 68;
export function targetPlayerCtrlStateUpdate(event: PlayerControlEvent): boolean {
let handledCase = false;
export function targetPlayerCtrlStateUpdate(event: PlayerControlEvent) {
switch (event) {
case PlayerControlEvent.Load: {
playerCtrlProgressBarBuffer.setAttribute("style", "width: 0px");
playerCtrlProgressBarProgress.setAttribute("style", "width: 0px");
playerCtrlProgressBarHandle.setAttribute("style", `left: ${playerCtrlProgressBar.offsetLeft}px`);
const volume = Math.round(player.getVolume() * playerCtrlVolumeBar.offsetWidth);
playerCtrlVolumeBarProgress.setAttribute("style", `width: ${volume}px`);
playerCtrlVolumeBarHandle.setAttribute("style", `left: ${volume + 8}px`);
if (isLive) {
playerCtrlLiveBadge.setAttribute("style", "display: block");
playerCtrlPosition.setAttribute("style", "display: none");
playerCtrlDuration.setAttribute("style", "display: none");
}
else {
playerCtrlLiveBadge.setAttribute("style", "display: none");
playerCtrlPosition.setAttribute("style", "display: block");
playerCtrlDuration.setAttribute("style", "display: block");
playerCtrlPosition.textContent = formatDuration(player.getCurrentTime());
playerCtrlDuration.innerHTML = formatDuration(player.getDuration());
}
if (player.isCaptionsSupported()) {
// Disabling receiver captions control on TV players
playerCtrlCaptions.setAttribute("style", "display: none");
// playerCtrlCaptions.setAttribute("style", "display: block");
videoCaptions.setAttribute("style", "display: block");
}
else {
playerCtrlCaptions.setAttribute("style", "display: none");
videoCaptions.setAttribute("style", "display: none");
player.enableCaptions(false);
}
playerCtrlStateUpdate(PlayerControlEvent.SetCaptions);
handledCase = true;
break;
}
default:
break;
}
return handledCase;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -18,3 +84,9 @@ export function targetKeyDownEventListener(event: any) {
if (window.webOSAPI.pendingPlay !== null) {
onPlay(null, window.webOSAPI.pendingPlay);
}
export {
captionsBaseHeightCollapsed,
captionsBaseHeightExpanded,
captionsLineHeight,
}

View file

@ -23,10 +23,17 @@
<div id="progressBarInteractiveArea" class="progressBarInteractiveArea" ></div>
</div>
<div class="leftButtonContainer">
<div id="action" class="play"></div>
<div class="positionContainer">
<div id="liveBadge" class="liveBadge" style="display: none">LIVE</div>
<div id="position" class="position">00:00</div>
<!-- <div id="durationSeparator" class="duration">/&nbsp&nbsp</div> -->
<div id="durationSeparator" class="duration"></div>
</div>
<div id="volume" class="volume_high"></div>
<div class="leftButtonContainer">
<div id="action" class="play iconSize"></div>
<div id="volume" class="volume_high iconSize"></div>
<div class="volumeContainer">
<div id="volumeBar" ref="volumeBar" class="volumeBar" ></div>
<div id="volumeBarProgress" class="volumeBarProgress" ></div>
@ -34,17 +41,18 @@
<div id="volumeBarInteractiveArea" class="volumeBarInteractiveArea" ></div>
</div>
<div class="positionContainer">
<!-- <div class="positionContainer">
<div id="liveBadge" class="liveBadge" style="display: none">LIVE</div>
<div id="position" class="position">00:00</div>
<div id="duration" class="duration">/&nbsp&nbsp00:00</div>
</div>
</div> -->
</div>
<div class="buttonContainer">
<!-- <div id="fullscreen" class="fullscreen_on"></div> -->
<div id="speed" class="speed"></div>
<div id="captions" class="captions_off"></div>
<!-- <div id="fullscreen" class="fullscreen_on iconSize"></div> -->
<div id="speed" class="speed iconSize"></div>
<div id="captions" class="captions_off iconSize"></div>
<div id="duration" class="duration">00:00</div>
</div>
<div id="speedMenu" class="speedMenu" style="display: none">

View file

@ -1 +1,182 @@
/* WebOS custom player styles */
.container {
height: 240px;
}
.iconSize {
width: 48px;
height: 48px;
background-size: cover;
}
#volume {
display: none;
}
.volumeContainer {
height: 48px;
width: 184px;
display: none;
}
.volumeBar {
left: 16px;
top: 20px;
height: 8px;
width: 152px;
}
.volumeBarInteractiveArea {
height: 48px;
width: 184px;
}
.volumeBarHandle {
left: 168px;
top: 8px;
width: 32px;
height: 32px;
box-shadow: 0px 64px 128px 0px rgba(0, 0, 0, 0.56), 0px 4px 42px 0px rgba(0, 0, 0, 0.55);
}
.volumeBarProgress {
left: 16px;
top: 20px;
height: 8px;
width: 152px;
}
.progressBarContainer {
bottom: 120px;
left: 32px;
right: 32px;
height: 8px;
padding-top: 20px;
padding-bottom: 20px;
}
.progressBarInteractiveArea {
height: 8px;
padding-top: 20px;
padding-bottom: 20px;
}
.progressBarChapterContainer {
bottom: 146px;
left: 48px;
right: 48px;
height: 8px;
}
.progressBar {
left: 16px;
width: calc(100% - 32px);
height: 8px;
}
.progressBarBuffer {
left: 16px;
bottom: 8px;
height: 8px;
}
.progressBarProgress {
left: 16px;
bottom: 16px;
height: 8px;
}
.progressBarPosition {
bottom: 50px;
padding: 4px 10px;
font-family: InterRegular;
font-size: 28px;
}
.progressBarHandle {
bottom: 20px;
width: 40px;
height: 40px;
margin-left: -16px;
margin-bottom: -16px;
}
.positionContainer {
position: absolute;
bottom: 48px;
left: 48px;
height: 48px;
font-family: InterRegular;
font-size: 28px;
flex-grow: unset;
}
.position {
font-family: InterRegular;
font-size: 28px;
/* margin-right: 20px; */
}
#duration {
font-family: InterRegular;
font-size: 28px;
}
.liveBadge {
padding: 4px 10px;
border-radius: 8px;
margin-right: 20px;
cursor: pointer;
}
.leftButtonContainer {
bottom: 48px;
left: 48px;
height: 48px;
/* right: 320px; */
right: 32px;
gap: 48px;
justify-content: center;
}
.buttonContainer {
bottom: 48px;
right: 48px;
height: 48px;
gap: 48px;
}
.captionsContainer {
/* display: none; */
/* position: relative; */
/* top: -200px; */
bottom: 320px;
/* margin: auto; */
/* text-align: center; */
/* font-family: InterVariable; */
font-family: InterRegular;
font-size: 56px;
/* font-style: normal; */
/* font-weight: 400; */
/* background-color: rgba(0, 0, 0, 0.5); */
padding: 0px 10px;
/* width: fit-content; */
/* user-select: none; */
/* transition: bottom 0.2s ease-in-out; */
}
#speed {
display: none;
}
#captions {
display: none;
}