mirror of
https://gitlab.com/futo-org/fcast.git
synced 2025-08-04 08:17:01 +00:00
Initial commit of WebOS receiver
This commit is contained in:
parent
b7e304b987
commit
90e1f4de1a
118 changed files with 18279 additions and 1746 deletions
95
receivers/common/web/main/Preload.ts
Normal file
95
receivers/common/web/main/Preload.ts
Normal file
|
@ -0,0 +1,95 @@
|
|||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
export {};
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
electronAPI: any;
|
||||
webOS: any;
|
||||
webOSDev: any;
|
||||
targetAPI: any;
|
||||
}
|
||||
}
|
||||
|
||||
let deviceInfo: any;
|
||||
|
||||
// @ts-ignore
|
||||
if (TARGET === 'electron') {
|
||||
// @ts-ignore
|
||||
const electronAPI = __non_webpack_require__('electron');
|
||||
|
||||
electronAPI.ipcRenderer.on("device-info", (_event, value) => {
|
||||
deviceInfo = value;
|
||||
})
|
||||
|
||||
electronAPI.contextBridge.exposeInMainWorld('targetAPI', {
|
||||
onDeviceInfo: (callback: any) => electronAPI.ipcRenderer.on("device-info", callback),
|
||||
getDeviceInfo: () => deviceInfo,
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
} else if (TARGET === 'webOS') {
|
||||
require('lib/webOSTVjs-1.2.10/webOSTV.js');
|
||||
require('lib/webOSTVjs-1.2.10/webOSTV-dev.js');
|
||||
const serviceId = 'com.futo.fcast.receiver.service';
|
||||
let onDeviceInfoCb: any;
|
||||
|
||||
const getDeviceInfoService = window.webOS.service.request(`luna://${serviceId}/`, {
|
||||
method:"getDeviceInfo",
|
||||
parameters: {},
|
||||
onSuccess: (message: any) => {
|
||||
console.log(`Main: getDeviceInfo ${JSON.stringify(message)}`);
|
||||
|
||||
deviceInfo = message.value;
|
||||
onDeviceInfoCb();
|
||||
},
|
||||
onFailure: (message: any) => {
|
||||
console.error(`Main: getDeviceInfo ${JSON.stringify(message)}`);
|
||||
},
|
||||
// onComplete: (message) => {},
|
||||
});
|
||||
|
||||
const playService = window.webOS.service.request(`luna://${serviceId}/`, {
|
||||
method:"play",
|
||||
parameters: {},
|
||||
onSuccess: (message: any) => {
|
||||
if (message.value.subscribed === true) {
|
||||
console.log('Main: Registered play handler with service');
|
||||
}
|
||||
else {
|
||||
console.log(`Main: Playing ${JSON.stringify(message)}`);
|
||||
getDeviceInfoService.cancel();
|
||||
playService.cancel();
|
||||
}
|
||||
},
|
||||
onFailure: (message: any) => {
|
||||
console.error(`Main: play ${JSON.stringify(message)}`);
|
||||
},
|
||||
subscribe: true,
|
||||
resubscribe: true
|
||||
});
|
||||
|
||||
window.targetAPI = {
|
||||
onDeviceInfo: (callback: any) => onDeviceInfoCb = callback,
|
||||
getDeviceInfo: () => deviceInfo,
|
||||
};
|
||||
|
||||
document.addEventListener('webOSRelaunch', (args: any) => {
|
||||
console.log(`Relaunching FCast Receiver with args: ${JSON.stringify(args)}`);
|
||||
|
||||
if (args.playData !== null) {
|
||||
if (getDeviceInfoService !== undefined) {
|
||||
getDeviceInfoService.cancel();
|
||||
}
|
||||
if (playService !== undefined) {
|
||||
playService.cancel();
|
||||
}
|
||||
|
||||
window.open('../player/index.html');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// @ts-ignore
|
||||
console.log(`Attempting to run FCast player on unsupported target: ${TARGET}`);
|
||||
}
|
48
receivers/common/web/main/Renderer.ts
Normal file
48
receivers/common/web/main/Renderer.ts
Normal file
|
@ -0,0 +1,48 @@
|
|||
|
||||
import QRCode from 'modules/qrcode';
|
||||
|
||||
window.targetAPI.onDeviceInfo(renderIPsAndQRCode);
|
||||
|
||||
if(window.targetAPI.getDeviceInfo()) {
|
||||
console.log("device info already present");
|
||||
renderIPsAndQRCode();
|
||||
}
|
||||
|
||||
function renderIPsAndQRCode() {
|
||||
const value = window.targetAPI.getDeviceInfo();
|
||||
console.log("device info", value);
|
||||
|
||||
const ipsElement = document.getElementById('ips');
|
||||
if (ipsElement) {
|
||||
ipsElement.innerHTML = `IPs<br>${value.addresses.join('<br>')}`;
|
||||
}
|
||||
|
||||
const fcastConfig = {
|
||||
name: value.name,
|
||||
addresses: value.addresses,
|
||||
services: [
|
||||
{ port: 46899, type: 0 }, //TCP
|
||||
{ port: 46898, type: 1 }, //WS
|
||||
]
|
||||
};
|
||||
|
||||
const json = JSON.stringify(fcastConfig);
|
||||
let base64 = btoa(json);
|
||||
base64 = base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
||||
const url = `fcast://r/${base64}`;
|
||||
console.log("qr", {json, url, base64});
|
||||
|
||||
const qrCodeElement = document.getElementById('qr-code');
|
||||
QRCode.toCanvas(qrCodeElement, url, {
|
||||
margin: 0,
|
||||
width: 256,
|
||||
color: {
|
||||
dark : "#000000",
|
||||
light : "#ffffff",
|
||||
},
|
||||
errorCorrectionLevel : "M",
|
||||
},
|
||||
(e) => {
|
||||
console.log(`Error rendering QR Code: ${e}`)
|
||||
});
|
||||
}
|
191
receivers/common/web/main/common.css
Normal file
191
receivers/common/web/main/common.css
Normal file
|
@ -0,0 +1,191 @@
|
|||
body, html {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#main-container {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.video {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.non-selectable {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
|
||||
background-color: rgba(20, 20, 20, 0.5);
|
||||
padding: 25px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid #2E2E2E;
|
||||
scrollbar-width: thin;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-weight: 700;
|
||||
line-height: 24px;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.card-title-separator {
|
||||
height: 1px;
|
||||
background: #2E2E2E;
|
||||
margin-top: 3px;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
#ui-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
gap: 15vw;
|
||||
|
||||
font-family: InterVariable;
|
||||
font-size: 20px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
#title-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#title-text {
|
||||
font-family: Outfit;
|
||||
font-size: 100px;
|
||||
font-weight: 800;
|
||||
text-align: center;
|
||||
|
||||
background-image: linear-gradient(180deg, #FFFFFF 5.9%, #D3D3D3 100%);
|
||||
background-clip: text;
|
||||
-webkit-background-clip:text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
#title-icon {
|
||||
width: 84px;
|
||||
height: 84px;
|
||||
|
||||
background-image: url(../assets/icons/app/icon.svg);
|
||||
background-size: cover;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
#connection-status {
|
||||
padding: 25px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#main-view {
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
#manual-connection-info {
|
||||
font-weight: 700;
|
||||
line-height: 24px;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
#manual-connection-info-separator {
|
||||
height: 1px;
|
||||
background: #2E2E2E;
|
||||
margin-top: 3px;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
#qr-code {
|
||||
display: flex;
|
||||
margin: 20px auto;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
#scan-to-connect {
|
||||
margin-top: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#waiting-for-connection, #ips, #automatic-discovery {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
#spinner {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
#window-can-be-closed {
|
||||
color: #666666;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
margin-bottom: 20px;
|
||||
|
||||
font-family: InterVariable;
|
||||
font-size: 18px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.lds-ring {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
.lds-ring div {
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
margin: 8px;
|
||||
border: 8px solid #fff;
|
||||
border-radius: 50%;
|
||||
animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
|
||||
border-color: #fff transparent transparent transparent;
|
||||
}
|
||||
.lds-ring div:nth-child(1) {
|
||||
animation-delay: -0.45s;
|
||||
}
|
||||
.lds-ring div:nth-child(2) {
|
||||
animation-delay: -0.3s;
|
||||
}
|
||||
.lds-ring div:nth-child(3) {
|
||||
animation-delay: -0.15s;
|
||||
}
|
||||
@keyframes lds-ring {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue