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:
parent
41f80880e4
commit
2df64dca89
20 changed files with 457 additions and 92 deletions
|
@ -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();
|
||||
};
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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">/  </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">/  00: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">
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue