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

WebOS 22 and 6.0 collection of fixes

This commit is contained in:
Michael Hollister 2024-12-17 22:59:41 -06:00
parent 902ccff8bc
commit d57e1368b1
13 changed files with 228 additions and 36 deletions

View file

@ -61,7 +61,31 @@ export class FCastSession {
const size = 1 + data.length; const size = 1 + data.length;
const header = Buffer.alloc(4 + 1); const header = Buffer.alloc(4 + 1);
header.writeUint32LE(size, 0);
// Web OS 22 and earlier node versions do not support `writeUint32LE`,
// so manually checking endianness and writing as LE
// @ts-ignore
if (TARGET === 'webOS') {
let uInt32 = new Uint32Array([0x11223344]);
let uInt8 = new Uint8Array(uInt32.buffer);
if(uInt8[0] === 0x44) {
// LE
header[0] = size & 0xFF;
header[1] = size & 0xFF00;
header[2] = size & 0xFF0000;
header[3] = size & 0xFF000000;
} else if (uInt8[0] === 0x11) {
// BE
header[0] = size & 0xFF000000;
header[1] = size & 0xFF0000;
header[2] = size & 0xFF00;
header[3] = size & 0xFF;
}
} else {
header.writeUint32LE(size, 0);
}
header[4] = opcode; header[4] = opcode;
let packet: Buffer; let packet: Buffer;

View file

@ -33,7 +33,19 @@ if (TARGET === 'electron') {
require('lib/webOSTVjs-1.2.10/webOSTV.js'); require('lib/webOSTVjs-1.2.10/webOSTV.js');
require('lib/webOSTVjs-1.2.10/webOSTV-dev.js'); require('lib/webOSTVjs-1.2.10/webOSTV-dev.js');
const serviceId = 'com.futo.fcast.receiver.service'; const serviceId = 'com.futo.fcast.receiver.service';
let onDeviceInfoCb: any; let onDeviceInfoCb = () => { console.log('Main: Callback not set while fetching device info'); };
const keepAliveService = window.webOS.service.request(`luna://${serviceId}/`, {
method:"keepAlive",
parameters: {},
onSuccess: (message: any) => {
console.log(`Main: keepAlive ${JSON.stringify(message)}`);
},
onFailure: (message: any) => {
console.error(`Main: keepAlive ${JSON.stringify(message)}`);
},
// onComplete: (message) => {},
});
const getDeviceInfoService = window.webOS.service.request(`luna://${serviceId}/`, { const getDeviceInfoService = window.webOS.service.request(`luna://${serviceId}/`, {
method:"getDeviceInfo", method:"getDeviceInfo",
@ -58,9 +70,16 @@ if (TARGET === 'electron') {
console.log('Main: Registered play handler with service'); console.log('Main: Registered play handler with service');
} }
else { else {
console.log(`Main: Playing ${JSON.stringify(message)}`); if (message.value !== undefined && message.value.playData !== undefined) {
getDeviceInfoService.cancel(); console.log(`Main: Playing ${JSON.stringify(message)}`);
playService.cancel(); getDeviceInfoService.cancel();
playService.cancel();
// WebOS 22 and earlier does not work well using the history API,
// so manually handling page navigation...
// history.pushState({}, '', '../main_window/index.html');
window.open('../player/index.html');
}
} }
}, },
onFailure: (message: any) => { onFailure: (message: any) => {
@ -71,14 +90,14 @@ if (TARGET === 'electron') {
}); });
window.targetAPI = { window.targetAPI = {
onDeviceInfo: (callback: any) => onDeviceInfoCb = callback, onDeviceInfo: (callback: () => void) => onDeviceInfoCb = callback,
getDeviceInfo: () => deviceInfo, getDeviceInfo: () => deviceInfo,
}; };
document.addEventListener('webOSRelaunch', (args: any) => { document.addEventListener('webOSRelaunch', (args: any) => {
console.log(`Relaunching FCast Receiver with args: ${JSON.stringify(args)}`); console.log(`Relaunching FCast Receiver with args: ${JSON.stringify(args)}`);
if (args.playData !== null) { if (args.playData !== undefined) {
if (getDeviceInfoService !== undefined) { if (getDeviceInfoService !== undefined) {
getDeviceInfoService.cancel(); getDeviceInfoService.cancel();
} }
@ -86,7 +105,9 @@ if (TARGET === 'electron') {
playService.cancel(); playService.cancel();
} }
history.pushState({}, '', '../main_window/index.html'); // WebOS 22 and earlier does not work well using the history API,
// so manually handling page navigation...
// history.pushState({}, '', '../main_window/index.html');
window.open('../player/index.html'); window.open('../player/index.html');
} }
}); });

View file

@ -43,8 +43,13 @@ function renderIPsAndQRCode() {
}, },
errorCorrectionLevel : "M", errorCorrectionLevel : "M",
}, },
(e) => { (err) => {
console.log(`Error rendering QR Code: ${e}`) if (err) {
console.error(`Error rendering QR Code: ${err}`);
}
else {
console.log(`Rendered QR Code`);
}
}); });
onQRCodeRendered(); onQRCodeRendered();

View file

@ -40,11 +40,23 @@ if (TARGET === 'electron') {
let onSeekCb, onSetVolumeCb, onSetSpeedCb; let onSeekCb, onSetVolumeCb, onSetSpeedCb;
let playerWindowOpen = false; let playerWindowOpen = false;
const keepAliveService = window.webOS.service.request(`luna://${serviceId}/`, {
method:"keepAlive",
parameters: {},
onSuccess: (message: any) => {
console.log(`Player: keepAlive ${JSON.stringify(message)}`);
},
onFailure: (message: any) => {
console.error(`Player: keepAlive ${JSON.stringify(message)}`);
},
// onComplete: (message) => {},
});
const playService = window.webOS.service.request(`luna://${serviceId}/`, { const playService = window.webOS.service.request(`luna://${serviceId}/`, {
method:"play", method:"play",
parameters: {}, parameters: {},
onSuccess: (message: any) => { onSuccess: (message: any) => {
console.log(JSON.stringify(message)); // console.log(JSON.stringify(message));
if (message.value.subscribed === true) { if (message.value.subscribed === true) {
console.log('Player: Registered play handler with service'); console.log('Player: Registered play handler with service');
} }
@ -122,8 +134,10 @@ if (TARGET === 'electron') {
setVolumeService.cancel(); setVolumeService.cancel();
setSpeedService.cancel(); setSpeedService.cancel();
// window.open('../main_window/index.html'); // WebOS 22 and earlier does not work well using the history API,
history.back(); // so manually handling page navigation...
// history.back();
window.open('../main_window/index.html');
} }
}, },
onFailure: (message: any) => { onFailure: (message: any) => {

View file

@ -258,6 +258,14 @@ function onPlay(_event, value: PlayMessage) {
hlsPlayer.on(Hls.Events.LEVEL_LOADED, (eventName, level: LevelLoadedData) => { hlsPlayer.on(Hls.Events.LEVEL_LOADED, (eventName, level: LevelLoadedData) => {
isLive = level.details.live; isLive = level.details.live;
isLivePosition = isLive ? true : false; isLivePosition = isLive ? true : false;
// Event can fire after video load and play initialization
if (isLive && playerCtrlLiveBadge.style.display === "none") {
playerCtrlLiveBadge.style.display = "block";
playerCtrlPosition.style.display = "none";
playerCtrlDurationSeparator.style.display = "none";
playerCtrlDuration.style.display = "none";
}
}); });
player = new Player(PlayerType.Hls, videoElement, hlsPlayer); player = new Player(PlayerType.Hls, videoElement, hlsPlayer);
@ -717,6 +725,10 @@ const volumeIncrement = 0.1;
function keyDownEventListener(event: any) { function keyDownEventListener(event: any) {
// console.log("KeyDown", event); // console.log("KeyDown", event);
const handledCase = targetKeyDownEventListener(event);
if (handledCase) {
return;
}
switch (event.code) { switch (event.code) {
case 'KeyF': case 'KeyF':
@ -729,15 +741,11 @@ function keyDownEventListener(event: any) {
event.preventDefault(); event.preventDefault();
break; break;
case 'ArrowLeft': case 'ArrowLeft':
// Skip back skipBack();
player.setCurrentTime(Math.max(player.getCurrentTime() - skipInterval, 0));
event.preventDefault(); event.preventDefault();
break; break;
case 'ArrowRight': case 'ArrowRight':
// Skip forward skipForward();
if (!isLivePosition) {
player.setCurrentTime(Math.min(player.getCurrentTime() + skipInterval, player.getDuration()));
}
event.preventDefault(); event.preventDefault();
break; break;
case "Home": case "Home":
@ -756,7 +764,7 @@ function keyDownEventListener(event: any) {
case 'KeyK': case 'KeyK':
case 'Space': case 'Space':
case 'Enter': case 'Enter':
// Pause/Continue // Play/pause toggle
if (player.isPaused()) { if (player.isPaused()) {
player.play(); player.play();
} else { } else {
@ -777,11 +785,20 @@ function keyDownEventListener(event: any) {
volumeChangeHandler(Math.max(player.getVolume() - volumeIncrement, 0)); volumeChangeHandler(Math.max(player.getVolume() - volumeIncrement, 0));
break; break;
default: default:
targetKeyDownEventListener(event);
break; break;
} }
} }
function skipBack() {
player.setCurrentTime(Math.max(player.getCurrentTime() - skipInterval, 0));
}
function skipForward() {
if (!isLivePosition) {
player.setCurrentTime(Math.min(player.getCurrentTime() + skipInterval, player.getDuration()));
}
}
document.addEventListener('keydown', keyDownEventListener); document.addEventListener('keydown', keyDownEventListener);
export { export {
@ -806,4 +823,6 @@ export {
onPlay, onPlay,
playerCtrlStateUpdate, playerCtrlStateUpdate,
formatDuration, formatDuration,
skipBack,
skipForward,
}; };

View file

@ -42,20 +42,26 @@ export function targetPlayerCtrlStateUpdate(event: PlayerControlEvent): boolean
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
export function targetKeyDownEventListener(event: any) { export function targetKeyDownEventListener(event: any): boolean {
let handledCase = false;
switch (event.code) { switch (event.code) {
case 'KeyF': case 'KeyF':
case 'F11': case 'F11':
playerCtrlStateUpdate(PlayerControlEvent.ToggleFullscreen); playerCtrlStateUpdate(PlayerControlEvent.ToggleFullscreen);
event.preventDefault(); event.preventDefault();
handledCase = true;
break; break;
case 'Escape': case 'Escape':
playerCtrlStateUpdate(PlayerControlEvent.ExitFullscreen); playerCtrlStateUpdate(PlayerControlEvent.ExitFullscreen);
event.preventDefault(); event.preventDefault();
handledCase = true;
break; break;
default: default:
break; break;
} }
return handledCase
}; };
export { export {

View file

@ -37,10 +37,16 @@ export class Main {
const serviceId = 'com.futo.fcast.receiver.service'; const serviceId = 'com.futo.fcast.receiver.service';
const service = new Service(serviceId); const service = new Service(serviceId);
// Not compatible with WebOS 22 and earlier simulator?
// Service will timeout and casting will disconnect if not forced to be kept alive // Service will timeout and casting will disconnect if not forced to be kept alive
let keepAlive; // let keepAlive;
service.activityManager.create("keepAlive", function(activity) { // service.activityManager.create("keepAlive", function(activity) {
keepAlive = activity; // keepAlive = activity;
// });
service.register("keepAlive", (_message: any) => {
Main.logger.info("In keepAlive callback");
// Do not respond to keep service alive
}); });
service.register("getDeviceInfo", (message: any) => { service.register("getDeviceInfo", (message: any) => {

View file

@ -9,5 +9,6 @@
"icon": "assets/icons/icon.png", "icon": "assets/icons/icon.png",
"largeIcon": "assets/icons/largeIcon.png", "largeIcon": "assets/icons/largeIcon.png",
"iconColor": "#0a62f5", "iconColor": "#0a62f5",
"splashBackground": "assets/images/splash.png" "splashBackground": "assets/images/splash.png",
"disableBackHistoryAPI": "true"
} }

View file

@ -1,6 +1,29 @@
import 'common/main/Preload'; import 'common/main/Preload';
enum RemoteKeyCode {
Stop = 413,
Rewind = 412,
Play = 415,
Pause = 19,
FastForward = 417,
Back = 461,
}
// Cannot go back to a state where user was previously casting a video, so exit. // Cannot go back to a state where user was previously casting a video, so exit.
window.onpopstate = () => { // window.onpopstate = () => {
window.webOS.platformBack(); // window.webOS.platformBack();
}; // };
document.addEventListener('keydown', (event: any) => {
// console.log("KeyDown", event);
switch (event.keyCode) {
// WebOS 22 and earlier does not work well using the history API,
// so manually handling page navigation...
case RemoteKeyCode.Back:
window.webOS.platformBack();
break;
default:
break;
}
});

View file

@ -2,14 +2,19 @@ import 'common/main/Renderer';
const backgroundVideo = document.getElementById('video-player'); const backgroundVideo = document.getElementById('video-player');
const loadingScreen = document.getElementById('loading-screen'); const loadingScreen = document.getElementById('loading-screen');
let backgroundVideoLoaded = false;
let qrCodeRendered = false; // WebOS 6.0 requires global scope for access during callback invocation
// eslint-disable-next-line no-var
var backgroundVideoLoaded: boolean;
// eslint-disable-next-line no-var
var qrCodeRendered: boolean;
backgroundVideo.onplaying = () => { backgroundVideo.onplaying = () => {
backgroundVideoLoaded = true; backgroundVideoLoaded = true;
if (backgroundVideoLoaded && qrCodeRendered) { if (backgroundVideoLoaded && qrCodeRendered) {
loadingScreen.style.display = 'none'; loadingScreen.style.display = 'none';
backgroundVideo.onplaying = null;
} }
}; };

View file

@ -5,6 +5,17 @@
#overlay { #overlay {
font-family: InterRegular; font-family: InterRegular;
font-size: 28px; font-size: 28px;
/* gap not supported in WebOS 6.0 */
gap: unset;
}
#main-view {
padding: 25px;
/* gap not supported in WebOS 6.0 */
gap: unset;
margin-right: 15vw;
} }
#title-text { #title-text {

View file

@ -17,12 +17,23 @@ import {
playerCtrlVolumeBarProgress, playerCtrlVolumeBarProgress,
videoCaptions, videoCaptions,
formatDuration, formatDuration,
skipBack,
skipForward,
} from 'common/player/Renderer'; } from 'common/player/Renderer';
const captionsBaseHeightCollapsed = 150; const captionsBaseHeightCollapsed = 150;
const captionsBaseHeightExpanded = 320; const captionsBaseHeightExpanded = 320;
const captionsLineHeight = 68; const captionsLineHeight = 68;
enum RemoteKeyCode {
Stop = 413,
Rewind = 412,
Play = 415,
Pause = 19,
FastForward = 417,
Back = 461,
}
export function targetPlayerCtrlStateUpdate(event: PlayerControlEvent): boolean { export function targetPlayerCtrlStateUpdate(event: PlayerControlEvent): boolean {
let handledCase = false; let handledCase = false;
@ -74,11 +85,57 @@ export function targetPlayerCtrlStateUpdate(event: PlayerControlEvent): boolean
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
export function targetKeyDownEventListener(event: any) { export function targetKeyDownEventListener(event: any): boolean {
switch (event.code) { let handledCase = false;
switch (event.keyCode) {
case RemoteKeyCode.Stop:
// history.back();
window.open('../main_window/index.html');
handledCase = true;
break;
case RemoteKeyCode.Rewind:
skipBack();
event.preventDefault();
handledCase = true;
break;
case RemoteKeyCode.Play:
if (player.isPaused()) {
player.play();
}
event.preventDefault();
handledCase = true;
break;
case RemoteKeyCode.Pause:
if (!player.isPaused()) {
player.pause();
}
event.preventDefault();
handledCase = true;
break;
case RemoteKeyCode.FastForward:
skipForward();
event.preventDefault();
handledCase = true;
break;
// WebOS 22 and earlier does not work well using the history API,
// so manually handling page navigation...
case RemoteKeyCode.Back:
// history.back();
window.open('../main_window/index.html');
event.preventDefault();
handledCase = true;
break;
default: default:
break; break;
} }
return handledCase;
}; };
if (window.webOSAPI.pendingPlay !== null) { if (window.webOSAPI.pendingPlay !== null) {

View file

@ -1,8 +1,8 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "ES2022", "target": "ES2015",
"module": "ES2022", "module": "ES2015",
"moduleResolution": "node", "moduleResolution": "node10",
"sourceMap": false, "sourceMap": false,
"emitDecoratorMetadata": true, "emitDecoratorMetadata": true,
"esModuleInterop": true, "esModuleInterop": true,