mirror of
https://gitlab.com/futo-org/fcast.git
synced 2025-07-30 05:46:59 +00:00
webOS: Reworked page navigation, service subscription, and resolution handling
This commit is contained in:
parent
0da73f1f5b
commit
a549296aca
16 changed files with 1425 additions and 374 deletions
|
@ -43,6 +43,21 @@ export class Main {
|
|||
|
||||
private static windowVisible: boolean = false;
|
||||
private static windowType: string = 'main';
|
||||
private static serviceChannelEvents = [
|
||||
'toast',
|
||||
'connect',
|
||||
'disconnect',
|
||||
'play',
|
||||
'pause',
|
||||
'resume',
|
||||
'stop',
|
||||
'seek',
|
||||
'setvolume',
|
||||
'setspeed',
|
||||
'setplaylistitem',
|
||||
'event_subscribed_keys_update'
|
||||
];
|
||||
private static serviceChannelEventTimestamps: Map<string, number> = new Map();
|
||||
|
||||
private static async play(message: PlayMessage) {
|
||||
Main.listeners.forEach(l => l.send(Opcode.PlayUpdate, new PlayUpdateMessage(Date.now(), message)));
|
||||
|
@ -62,6 +77,9 @@ export class Main {
|
|||
logger.info(`Launch response: ${JSON.stringify(response)}`);
|
||||
logger.info(`Relaunching FCast Receiver with args: ${messageInfo.rendererEvent} ${JSON.stringify(messageInfo.rendererMessage)}`);
|
||||
});
|
||||
|
||||
Main.windowVisible = true;
|
||||
Main.windowType = 'player';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -82,37 +100,124 @@ export class Main {
|
|||
|
||||
Main.tcpListenerService = new TcpListenerService();
|
||||
Main.webSocketListenerService = new WebSocketListenerService();
|
||||
|
||||
Main.emitter = new EventEmitter();
|
||||
|
||||
const voidCb = (message: any) => { message.respond({ returnValue: true, value: {} }); };
|
||||
const objectCb = (message: any, value: any) => { message.respond({ returnValue: true, value: value }); };
|
||||
service.register('service_channel', (message: any) => {
|
||||
if (message.isSubscription) {
|
||||
Main.serviceChannelEvents.forEach((event) => {
|
||||
Main.emitter.on(event, (value) => {
|
||||
const timestamp = Date.now();
|
||||
const lastTimestamp = Main.serviceChannelEventTimestamps.get(event) ? Main.serviceChannelEventTimestamps.get(event) : -1;
|
||||
|
||||
registerService(service, 'toast', (message: any) => { return objectCb.bind(this, message) });
|
||||
registerService(service, 'connect', (message: any) => { return objectCb.bind(this, message) });
|
||||
registerService(service, 'disconnect', (message: any) => { return objectCb.bind(this, message) });
|
||||
registerService(service, 'play', (message: any) => { return objectCb.bind(this, message) });
|
||||
registerService(service, 'pause', (message: any) => { return voidCb.bind(this, message) });
|
||||
registerService(service, 'resume', (message: any) => { return voidCb.bind(this, message) });
|
||||
registerService(service, 'stop', (message: any) => { return voidCb.bind(this, message) });
|
||||
registerService(service, 'seek', (message: any) => { return objectCb.bind(this, message) });
|
||||
registerService(service, 'setvolume', (message: any) => { return objectCb.bind(this, message) });
|
||||
registerService(service, 'setspeed', (message: any) => { return objectCb.bind(this, message) });
|
||||
registerService(service, 'setplaylistitem', (message: any) => { return objectCb.bind(this, message) });
|
||||
registerService(service, 'event_subscribed_keys_update', (message: any) => { return objectCb.bind(this, message) });
|
||||
if (lastTimestamp < timestamp) {
|
||||
Main.serviceChannelEventTimestamps.set(event, timestamp);
|
||||
message.respond({ returnValue: true, subscriptionId: message.payload.subscriptionId, timestamp: timestamp, event: event, value: value });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
message.respond({ returnValue: true, subscriptionId: message.payload.subscriptionId, timestamp: Date.now(), event: 'register', value: { subscribed: true }});
|
||||
},
|
||||
(message: any) => {
|
||||
logger.info(`Canceled 'service_channel' service subscriber`);
|
||||
Main.serviceChannelEvents.forEach((event) => {
|
||||
Main.emitter.removeAllListeners(event);
|
||||
});
|
||||
|
||||
message.respond({ returnValue: true, value: {} });
|
||||
});
|
||||
|
||||
service.register('app_channel', (message: any) => {
|
||||
switch (message.payload.event) {
|
||||
case 'send_playback_error': {
|
||||
const value: PlaybackErrorMessage = message.payload.value;
|
||||
Main.listeners.forEach(l => l.send(Opcode.PlaybackError, value));
|
||||
break;
|
||||
}
|
||||
|
||||
case 'send_playback_update': {
|
||||
const value: PlaybackUpdateMessage = message.payload.value;
|
||||
Main.listeners.forEach(l => l.send(Opcode.PlaybackUpdate, value));
|
||||
break;
|
||||
}
|
||||
|
||||
case 'send_volume_update': {
|
||||
const value: VolumeUpdateMessage = message.payload.value;
|
||||
Main.cache.playerVolume = value.volume;
|
||||
Main.listeners.forEach(l => l.send(Opcode.VolumeUpdate, value));
|
||||
break;
|
||||
}
|
||||
|
||||
case 'send_event': {
|
||||
const value: EventMessage = message.payload.value;
|
||||
Main.listeners.forEach(l => l.send(Opcode.Event, value));
|
||||
break;
|
||||
}
|
||||
|
||||
case 'play_request': {
|
||||
const value: PlayMessage = message.payload.value.message;
|
||||
const playlistIndex: number = message.payload.value.playlistIndex;
|
||||
|
||||
logger.debug(`Received play request for index ${playlistIndex}:`, value);
|
||||
value.url = Main.mediaCache?.has(playlistIndex) ? Main.mediaCache?.getUrl(playlistIndex) : value.url;
|
||||
Main.mediaCache?.cacheItems(playlistIndex);
|
||||
Main.play(value);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'get_sessions': {
|
||||
// Having to mix and match session ids and ip addresses until querying websocket remote addresses is fixed
|
||||
message.respond({
|
||||
returnValue: true,
|
||||
value: [].concat(Main.tcpListenerService.getSenders(), Main.webSocketListenerService.getSessions())
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
case 'network_changed': {
|
||||
logger.info('Network interfaces have changed', message);
|
||||
Main.discoveryService.stop();
|
||||
Main.discoveryService.start();
|
||||
|
||||
if (message.payload.value.fallback) {
|
||||
message.respond({
|
||||
returnValue: true,
|
||||
value: getAllIPv4Addresses()
|
||||
});
|
||||
}
|
||||
else {
|
||||
message.respond({ returnValue: true, value: {} });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
case 'visibility_changed': {
|
||||
logger.info('Window visibility has changed', message.payload.value);
|
||||
Main.windowVisible = !message.payload.value.hidden;
|
||||
Main.windowType = message.payload.value.window;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
message.respond({ returnValue: true, value: { success: true } });
|
||||
});
|
||||
|
||||
Main.listeners = [Main.tcpListenerService, Main.webSocketListenerService];
|
||||
Main.listeners.forEach(l => {
|
||||
l.emitter.on("play", (message: PlayMessage) => Main.play(message));
|
||||
l.emitter.on("pause", () => Main.emitter.emit('pause'));
|
||||
l.emitter.on("resume", () => Main.emitter.emit('resume'));
|
||||
l.emitter.on("stop", () => Main.emitter.emit('stop'));
|
||||
l.emitter.on("seek", (message: SeekMessage) => Main.emitter.emit('seek', message));
|
||||
l.emitter.on("setvolume", (message: SetVolumeMessage) => {
|
||||
l.emitter.on('play', (message: PlayMessage) => Main.play(message));
|
||||
l.emitter.on('pause', () => Main.emitter.emit('pause'));
|
||||
l.emitter.on('resume', () => Main.emitter.emit('resume'));
|
||||
l.emitter.on('stop', () => Main.emitter.emit('stop'));
|
||||
l.emitter.on('seek', (message: SeekMessage) => Main.emitter.emit('seek', message));
|
||||
l.emitter.on('setvolume', (message: SetVolumeMessage) => {
|
||||
Main.cache.playerVolume = message.volume;
|
||||
Main.emitter.emit('setvolume', message);
|
||||
});
|
||||
l.emitter.on("setspeed", (message: SetSpeedMessage) => Main.emitter.emit('setspeed', message));
|
||||
l.emitter.on('setspeed', (message: SetSpeedMessage) => Main.emitter.emit('setspeed', message));
|
||||
|
||||
l.emitter.on('connect', (message) => {
|
||||
ConnectionMonitor.onConnect(l, message, l instanceof WebSocketListenerService, () => {
|
||||
|
@ -133,7 +238,7 @@ export class Main {
|
|||
l.emitter.on('initial', (message) => {
|
||||
logger.info(`Received 'Initial' message from sender: ${message}`);
|
||||
});
|
||||
l.emitter.on("setplaylistitem", (message: SetPlaylistItemMessage) => Main.emitter.emit('setplaylistitem', message));
|
||||
l.emitter.on('setplaylistitem', (message: SetPlaylistItemMessage) => Main.emitter.emit('setplaylistitem', message));
|
||||
l.emitter.on('subscribeevent', (message) => {
|
||||
const subscribeData = l.subscribeEvent(message.sessionId, message.body.event);
|
||||
|
||||
|
@ -150,75 +255,6 @@ export class Main {
|
|||
});
|
||||
l.start();
|
||||
});
|
||||
|
||||
service.register("send_playback_error", (message: any) => {
|
||||
const value: PlaybackErrorMessage = message.payload.error;
|
||||
Main.listeners.forEach(l => l.send(Opcode.PlaybackError, value));
|
||||
message.respond({ returnValue: true, value: { success: true } });
|
||||
});
|
||||
|
||||
service.register("send_playback_update", (message: any) => {
|
||||
// logger.info("In send_playback_update callback");
|
||||
const value: PlaybackUpdateMessage = message.payload.update;
|
||||
Main.listeners.forEach(l => l.send(Opcode.PlaybackUpdate, value));
|
||||
message.respond({ returnValue: true, value: { success: true } });
|
||||
});
|
||||
|
||||
service.register("send_volume_update", (message: any) => {
|
||||
const value: VolumeUpdateMessage = message.payload.update;
|
||||
Main.cache.playerVolume = value.volume;
|
||||
Main.listeners.forEach(l => l.send(Opcode.VolumeUpdate, value));
|
||||
message.respond({ returnValue: true, value: { success: true } });
|
||||
});
|
||||
|
||||
service.register("send_event", (message: any) => {
|
||||
const value: EventMessage = message.payload.event;
|
||||
Main.listeners.forEach(l => l.send(Opcode.Event, value));
|
||||
message.respond({ returnValue: true, value: { success: true } });
|
||||
});
|
||||
|
||||
service.register("play_request", (message: any) => {
|
||||
const value: PlayMessage = message.payload.message;
|
||||
const playlistIndex: number = message.payload.playlistIndex;
|
||||
|
||||
logger.debug(`Received play request for index ${playlistIndex}:`, value);
|
||||
value.url = Main.mediaCache?.has(playlistIndex) ? Main.mediaCache?.getUrl(playlistIndex) : value.url;
|
||||
Main.mediaCache?.cacheItems(playlistIndex);
|
||||
Main.play(value);
|
||||
|
||||
message.respond({ returnValue: true, value: { success: true } });
|
||||
});
|
||||
|
||||
// Having to mix and match session ids and ip addresses until querying websocket remote addresses is fixed
|
||||
service.register("get_sessions", (message: any) => {
|
||||
message.respond({
|
||||
returnValue: true,
|
||||
value: [].concat(Main.tcpListenerService.getSenders(), Main.webSocketListenerService.getSessions())
|
||||
});
|
||||
});
|
||||
|
||||
service.register("network_changed", (message: any) => {
|
||||
logger.info('Network interfaces have changed', message);
|
||||
Main.discoveryService.stop();
|
||||
Main.discoveryService.start();
|
||||
|
||||
if (message.payload.fallback) {
|
||||
message.respond({
|
||||
returnValue: true,
|
||||
value: getAllIPv4Addresses()
|
||||
});
|
||||
}
|
||||
else {
|
||||
message.respond({ returnValue: true, value: {} });
|
||||
}
|
||||
});
|
||||
|
||||
service.register("visibility_changed", (message: any) => {
|
||||
logger.info('Window visibility has changed', message.payload);
|
||||
Main.windowVisible = !message.payload.hidden;
|
||||
Main.windowType = message.payload.window;
|
||||
message.respond({ returnValue: true, value: {} });
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
logger.error("Error initializing service:", err);
|
||||
|
@ -251,23 +287,6 @@ export async function errorHandler(error: Error) {
|
|||
Main.emitter.emit('toast', { message: error, icon: ToastIcon.ERROR });
|
||||
}
|
||||
|
||||
function registerService(service: Service, method: string, callback: (message: any) => any) {
|
||||
let callbackRef = null;
|
||||
service.register(method, (message: any) => {
|
||||
if (message.isSubscription) {
|
||||
callbackRef = callback(message);
|
||||
Main.emitter.on(method, callbackRef);
|
||||
}
|
||||
|
||||
message.respond({ returnValue: true, value: { subscribed: true }});
|
||||
},
|
||||
(message: any) => {
|
||||
logger.info(`Canceled ${method} service subscriber`);
|
||||
Main.emitter.removeAllListeners(method);
|
||||
message.respond({ returnValue: true, value: message.payload });
|
||||
});
|
||||
}
|
||||
|
||||
// Fallback for simulator or TV devices that don't work with the luna://com.palm.connectionmanager/getStatus method
|
||||
function getAllIPv4Addresses() {
|
||||
const interfaces = os.networkInterfaces();
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"version": "2.0.0",
|
||||
"vendor": "FUTO",
|
||||
"type": "web",
|
||||
"main": "main_window/index.html",
|
||||
"main": "index.html",
|
||||
"title": "FCast Receiver",
|
||||
"appDescription": "FCast Receiver",
|
||||
"icon": "assets/icons/icon.png",
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
const logger = window.targetAPI.logger;
|
||||
import { v4 as uuidv4 } from 'modules/uuid';
|
||||
import { Logger, LoggerType } from 'common/Logger';
|
||||
require('lib/webOSTVjs-1.2.10/webOSTV.js');
|
||||
require('lib/webOSTVjs-1.2.10/webOSTV-dev.js');
|
||||
|
||||
const logger = new Logger('Common', LoggerType.FRONTEND);
|
||||
const serviceId = 'com.futo.fcast.receiver.service';
|
||||
|
||||
export enum RemoteKeyCode {
|
||||
|
@ -10,59 +15,104 @@ export enum RemoteKeyCode {
|
|||
Back = 461,
|
||||
}
|
||||
|
||||
export function requestService(method: string, successCb: (message: any) => void, failureCb?: (message: any) => void, onCompleteCb?: (message: any) => void): any {
|
||||
return window.webOS.service.request(`luna://${serviceId}/`, {
|
||||
method: method,
|
||||
parameters: {},
|
||||
onSuccess: (message: any) => {
|
||||
if (message.value?.subscribed === true) {
|
||||
logger.info(`requestService: Registered ${method} handler with service`);
|
||||
}
|
||||
else {
|
||||
successCb(message);
|
||||
}
|
||||
},
|
||||
onFailure: (message: any) => {
|
||||
logger.error(`requestService: ${method} ${JSON.stringify(message)}`);
|
||||
export class ServiceManager {
|
||||
private static serviceChannelSuccessCbHandler?: (message: any) => void;
|
||||
private static serviceChannelFailureCbHandler?: (message: any) => void;
|
||||
private static serviceChannelCompleteCbHandler?: (message: any) => void;
|
||||
|
||||
if (failureCb) {
|
||||
failureCb(message);
|
||||
}
|
||||
},
|
||||
onComplete: (message: any) => {
|
||||
if (onCompleteCb) {
|
||||
onCompleteCb(message);
|
||||
}
|
||||
},
|
||||
subscribe: true,
|
||||
resubscribe: true
|
||||
});
|
||||
}
|
||||
|
||||
export function callService(method: string, parameters?: any, successCb?: (message: any) => void, failureCb?: (message: any) => void, onCompleteCb?: (message: any) => void) {
|
||||
return window.webOS.service.request(`luna://${serviceId}/`, {
|
||||
method: method,
|
||||
parameters: parameters,
|
||||
constructor() {
|
||||
// @ts-ignore
|
||||
window.webOS.service.request(`luna://${serviceId}/`, {
|
||||
method: 'service_channel',
|
||||
parameters: { subscriptionId: uuidv4() },
|
||||
onSuccess: (message: any) => {
|
||||
if (successCb) {
|
||||
successCb(message);
|
||||
if (message.value?.subscribed === true) {
|
||||
logger.info(`requestService: Registered 'service_channel' handler with service`);
|
||||
}
|
||||
else if (ServiceManager.serviceChannelSuccessCbHandler) {
|
||||
ServiceManager.serviceChannelSuccessCbHandler(message);
|
||||
}
|
||||
},
|
||||
onFailure: (message: any) => {
|
||||
logger.error(`callService: ${method} ${JSON.stringify(message)}`);
|
||||
logger.error('Error subscribing to the service_channel:', message);
|
||||
|
||||
if (failureCb) {
|
||||
failureCb(message);
|
||||
if (ServiceManager.serviceChannelFailureCbHandler) {
|
||||
ServiceManager.serviceChannelFailureCbHandler(message);
|
||||
}
|
||||
},
|
||||
onComplete: (message: any) => {
|
||||
if (onCompleteCb) {
|
||||
onCompleteCb(message);
|
||||
if (ServiceManager.serviceChannelCompleteCbHandler) {
|
||||
ServiceManager.serviceChannelCompleteCbHandler(message);
|
||||
}
|
||||
},
|
||||
subscribe: false,
|
||||
resubscribe: false
|
||||
});
|
||||
subscribe: true,
|
||||
resubscribe: true
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public subscribeToServiceChannel(successCb: (message: any) => void, failureCb?: (message: any) => void, onCompleteCb?: (message: any) => void) {
|
||||
ServiceManager.serviceChannelSuccessCbHandler = successCb;
|
||||
ServiceManager.serviceChannelFailureCbHandler = failureCb;
|
||||
ServiceManager.serviceChannelCompleteCbHandler = onCompleteCb;
|
||||
}
|
||||
|
||||
public call(method: string, parameters?: any, successCb?: (message: any) => void, failureCb?: (message: any) => void, onCompleteCb?: (message: any) => void) {
|
||||
// @ts-ignore
|
||||
const service = window.webOS.service.request(`luna://${serviceId}/`, {
|
||||
method: 'app_channel',
|
||||
parameters: { event: method, value: parameters },
|
||||
onSuccess: (message: any) => {
|
||||
if (successCb) {
|
||||
successCb(message);
|
||||
}
|
||||
},
|
||||
onFailure: (message: any) => {
|
||||
logger.error(`callService: ${method} ${JSON.stringify(message)}`);
|
||||
|
||||
if (failureCb) {
|
||||
failureCb(message);
|
||||
}
|
||||
},
|
||||
onComplete: (message: any) => {
|
||||
if (onCompleteCb) {
|
||||
onCompleteCb(message);
|
||||
}
|
||||
},
|
||||
subscribe: false,
|
||||
resubscribe: false
|
||||
});
|
||||
|
||||
return service;
|
||||
}
|
||||
}
|
||||
|
||||
// CSS media queries do not work on older webOS versions...
|
||||
export function initializeWindowSizeStylesheet() {
|
||||
const resolution = sessionStorage.getItem('resolution');
|
||||
|
||||
if (resolution) {
|
||||
window.onload = () => {
|
||||
if (resolution == '1920x1080') {
|
||||
document.head.insertAdjacentHTML('beforeend', '<link rel="stylesheet" href="./1920x1080.css" />');
|
||||
}
|
||||
else {
|
||||
document.head.insertAdjacentHTML('beforeend', '<link rel="stylesheet" href="./1280x720.css" />');
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
window.onresize = () => {
|
||||
if (window.innerWidth >= 1920 && window.innerHeight >= 1080) {
|
||||
sessionStorage.setItem('resolution', '1920x1080');
|
||||
document.head.insertAdjacentHTML('beforeend', '<link rel="stylesheet" href="./1920x1080.css" />');
|
||||
}
|
||||
else {
|
||||
sessionStorage.setItem('resolution', '1280x720');
|
||||
document.head.insertAdjacentHTML('beforeend', '<link rel="stylesheet" href="./1280x720.css" />');
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function targetKeyUpEventListener(event: KeyboardEvent): { handledCase: boolean, key: string } {
|
||||
|
|
32
receivers/webos/fcast-receiver/src/Main.ts
Normal file
32
receivers/webos/fcast-receiver/src/Main.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { Logger, LoggerType } from 'common/Logger';
|
||||
import { ServiceManager } from 'lib/common';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
webOSApp: any;
|
||||
}
|
||||
}
|
||||
|
||||
const logger = new Logger('Main', LoggerType.FRONTEND);
|
||||
const webPage: HTMLIFrameElement = document.getElementById('page') as HTMLIFrameElement;
|
||||
let launchHandlerCallback = () => { logger.warn('No (re)launch handler set'); };
|
||||
|
||||
function loadPage(path: string) {
|
||||
// @ts-ignore
|
||||
webPage.src = path;
|
||||
}
|
||||
|
||||
// We are embedding iframe element and using that for page navigation. This preserves a global JS context
|
||||
// so bugs related to oversubscribing/canceling services are worked around by only subscribing once to
|
||||
// required services
|
||||
logger.info('Starting webOS application')
|
||||
|
||||
window.webOSApp = {
|
||||
serviceManager: new ServiceManager(),
|
||||
setLaunchHandler: (callback: () => void) => launchHandlerCallback = callback,
|
||||
loadPage: loadPage
|
||||
};
|
||||
|
||||
document.addEventListener('webOSLaunch', launchHandlerCallback);
|
||||
document.addEventListener('webOSRelaunch', launchHandlerCallback);
|
||||
loadPage('./main_window/index.html');
|
17
receivers/webos/fcast-receiver/src/index.html
Normal file
17
receivers/webos/fcast-receiver/src/index.html
Normal file
|
@ -0,0 +1,17 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
iframe, body, html {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
border: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<iframe id="page", src="./main_window/index.html"></iframe>
|
||||
<script src="./main.js"></script>
|
||||
</body>
|
||||
</html>
|
101
receivers/webos/fcast-receiver/src/main/1280x720.css
Normal file
101
receivers/webos/fcast-receiver/src/main/1280x720.css
Normal file
|
@ -0,0 +1,101 @@
|
|||
|
||||
/* @media only screen and ((max-width: 1279px) or (max-height: 719px)) { */
|
||||
.card {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
line-height: 18px;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.card-title-separator {
|
||||
margin: 3px 0px;
|
||||
}
|
||||
|
||||
.iconSize {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
#overlay {
|
||||
gap: 10vw;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
#title-text {
|
||||
font-size: 80px;
|
||||
}
|
||||
|
||||
#title-icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
#connection-status {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
#connection-error-icon {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#connection-information-loading-text {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
#scan-to-connect {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#qr-code {
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
margin: 15px auto;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
#ips {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.ip-entry-text {
|
||||
margin-top: 1.5px;
|
||||
margin-bottom: 1.5px;
|
||||
}
|
||||
|
||||
#window-can-be-closed {
|
||||
margin-bottom: 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.lds-ring {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
.lds-ring div {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
#connection-check {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
#toast-notification {
|
||||
padding: 4px;
|
||||
top: -100px;
|
||||
}
|
||||
|
||||
#toast-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
#toast-text {
|
||||
font-size: 18px;
|
||||
}
|
||||
/* } */
|
204
receivers/webos/fcast-receiver/src/main/1920x1080.css
Normal file
204
receivers/webos/fcast-receiver/src/main/1920x1080.css
Normal file
|
@ -0,0 +1,204 @@
|
|||
|
||||
/* @media only screen and ((max-width: 1919px) or (max-height: 1079px)) { */
|
||||
.card {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
line-height: 20px;
|
||||
margin: 5px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.card-title-separator {
|
||||
margin: 3px 0px;
|
||||
}
|
||||
|
||||
.iconSize {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
#overlay {
|
||||
gap: 12.5vw;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
#title-text {
|
||||
font-size: 100px;
|
||||
}
|
||||
|
||||
#title-icon {
|
||||
width: 84px;
|
||||
height: 84px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
#connection-status {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
#connection-error-icon {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#connection-information-loading-text {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
#scan-to-connect {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#qr-code {
|
||||
width: 192px;
|
||||
height: 192px;
|
||||
margin: 15px auto;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
#ips {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.ip-entry-text {
|
||||
margin-top: 4px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
#window-can-be-closed {
|
||||
margin-bottom: 15px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.lds-ring {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
.lds-ring div {
|
||||
width: 84px;
|
||||
height: 84px;
|
||||
}
|
||||
|
||||
#connection-check {
|
||||
width: 84px;
|
||||
height: 84px;
|
||||
margin: 24px;
|
||||
}
|
||||
|
||||
#toast-notification {
|
||||
padding: 8px;
|
||||
top: -140px;
|
||||
}
|
||||
|
||||
#toast-icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
#toast-text {
|
||||
font-size: 20px;
|
||||
}
|
||||
/* } */
|
||||
|
||||
@media only screen and ((max-width: 1279px) or (max-height: 719px)) {
|
||||
.card {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
line-height: 18px;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.card-title-separator {
|
||||
margin: 3px 0px;
|
||||
}
|
||||
|
||||
.iconSize {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
#overlay {
|
||||
gap: 10vw;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
#title-text {
|
||||
font-size: 80px;
|
||||
}
|
||||
|
||||
#title-icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
#connection-status {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
#connection-error-icon {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#connection-information-loading-text {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
#scan-to-connect {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#qr-code {
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
margin: 15px auto;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
#ips {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.ip-entry-text {
|
||||
margin-top: 1.5px;
|
||||
margin-bottom: 1.5px;
|
||||
}
|
||||
|
||||
#window-can-be-closed {
|
||||
margin-bottom: 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.lds-ring {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
.lds-ring div {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
#connection-check {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
#toast-notification {
|
||||
padding: 4px;
|
||||
top: -100px;
|
||||
}
|
||||
|
||||
#toast-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
#toast-text {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
|
@ -3,23 +3,56 @@
|
|||
import { preloadData } from 'common/main/Preload';
|
||||
import { ToastIcon } from 'common/components/Toast';
|
||||
import { EventMessage } from 'common/Packets';
|
||||
import { callService, requestService } from 'lib/common';
|
||||
import { ServiceManager, initializeWindowSizeStylesheet } from 'lib/common';
|
||||
require('lib/webOSTVjs-1.2.10/webOSTV.js');
|
||||
require('lib/webOSTVjs-1.2.10/webOSTV-dev.js');
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
targetAPI: any;
|
||||
webOSApp: any;
|
||||
}
|
||||
}
|
||||
|
||||
const logger = window.targetAPI.logger;
|
||||
|
||||
try {
|
||||
const serviceId = 'com.futo.fcast.receiver.service';
|
||||
let getSessionsService = null;
|
||||
let networkChangedService = null;
|
||||
let visibilityChangedService = null;
|
||||
initializeWindowSizeStylesheet();
|
||||
|
||||
const serviceManager: ServiceManager = window.parent.webOSApp.serviceManager;
|
||||
serviceManager.subscribeToServiceChannel((message: any) => {
|
||||
switch (message.event) {
|
||||
case 'toast':
|
||||
preloadData.onToastCb(message.value.message, message.value.icon, message.value.duration);
|
||||
break;
|
||||
|
||||
case 'event_subscribed_keys_update':
|
||||
preloadData.onEventSubscribedKeysUpdate(message.value);
|
||||
break;
|
||||
|
||||
case 'connect':
|
||||
preloadData.onConnectCb(null, message.value);
|
||||
break;
|
||||
|
||||
case 'disconnect':
|
||||
preloadData.onDisconnectCb(null, message.value);
|
||||
break;
|
||||
|
||||
case 'play':
|
||||
logger.info(`Main: Playing ${JSON.stringify(message)}`);
|
||||
play(message.value);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
const toastService = requestService('toast', (message: any) => { preloadData.onToastCb(message.value.message, message.value.icon, message.value.duration); });
|
||||
const getDeviceInfoService = window.webOSDev.connection.getStatus({
|
||||
onSuccess: (message: any) => {
|
||||
logger.info('Network info status message', message);
|
||||
const deviceName = 'FCast-LGwebOSTV';
|
||||
const connections = [];
|
||||
const connections: any[] = [];
|
||||
let fallback = true;
|
||||
|
||||
if (message.wired.state !== 'disconnected') {
|
||||
|
@ -35,7 +68,10 @@ try {
|
|||
}
|
||||
|
||||
if (fallback) {
|
||||
networkChangedService = callService('network_changed', { fallback: fallback }, (message: any) => {
|
||||
const ipsIfaceName = document.getElementById('ips-iface-name');
|
||||
ipsIfaceName.style.display = 'none';
|
||||
|
||||
serviceManager.call('network_changed', { fallback: fallback }, (message: any) => {
|
||||
logger.info('Fallback network interfaces', message);
|
||||
for (const ipAddr of message.value) {
|
||||
connections.push({ type: 'wired', name: 'Ethernet', address: ipAddr });
|
||||
|
@ -46,14 +82,10 @@ try {
|
|||
}, (message: any) => {
|
||||
logger.error('Main: preload - error fetching network interfaces', message);
|
||||
preloadData.onToastCb('Error detecting network interfaces', ToastIcon.ERROR);
|
||||
}, () => {
|
||||
networkChangedService = null;
|
||||
});
|
||||
}
|
||||
else {
|
||||
networkChangedService = callService('network_changed', { fallback: fallback }, null, null, () => {
|
||||
networkChangedService = null;
|
||||
});
|
||||
serviceManager.call('network_changed', { fallback: fallback });
|
||||
preloadData.deviceInfo = { name: deviceName, interfaces: connections };
|
||||
preloadData.onDeviceInfoCb();
|
||||
}
|
||||
|
@ -66,74 +98,39 @@ try {
|
|||
resubscribe: true
|
||||
});
|
||||
|
||||
const onEventSubscribedKeysUpdateService = requestService('event_subscribed_keys_update', (message: any) => { preloadData.onEventSubscribedKeysUpdate(message.value); });
|
||||
window.targetAPI.getSessions(() => {
|
||||
return new Promise((resolve, reject) => {
|
||||
getSessionsService = callService('get_sessions', {}, (message: any) => resolve(message.value), (message: any) => reject(message));
|
||||
serviceManager.call('get_sessions', {}, (message: any) => resolve(message.value), (message: any) => reject(message));
|
||||
});
|
||||
});
|
||||
|
||||
const onConnectService = requestService('connect', (message: any) => { preloadData.onConnectCb(null, message.value); });
|
||||
const onDisconnectService = requestService('disconnect', (message: any) => { preloadData.onDisconnectCb(null, message.value); });
|
||||
preloadData.sendEventCb = (event: EventMessage) => {
|
||||
window.webOS.service.request(`luna://${serviceId}/`, {
|
||||
method: 'send_event',
|
||||
parameters: { event },
|
||||
onSuccess: () => {},
|
||||
onFailure: (message: any) => { logger.error(`Player: send_event ${JSON.stringify(message)}`); },
|
||||
});
|
||||
serviceManager.call('send_event', event, null, (message: any) => { logger.error(`Player: send_event ${JSON.stringify(message)}`); });
|
||||
};
|
||||
|
||||
const playService = requestService('play', (message: any) => {
|
||||
logger.info(`Main: Playing ${JSON.stringify(message)}`);
|
||||
play(message.value);
|
||||
});
|
||||
|
||||
const launchHandler = () => {
|
||||
const params = window.webOSDev.launchParams();
|
||||
logger.info(`Main: (Re)launching FCast Receiver with args: ${JSON.stringify(params)}`);
|
||||
|
||||
// WebOS 6.0 and earlier: Timestamp tracking seems to be necessary as launch event is raised regardless if app is in foreground or not
|
||||
const lastTimestamp = Number(localStorage.getItem('lastTimestamp'));
|
||||
const lastTimestamp = Number(sessionStorage.getItem('lastTimestamp'));
|
||||
if (params.messageInfo !== undefined && params.timestamp != lastTimestamp) {
|
||||
localStorage.setItem('lastTimestamp', params.timestamp);
|
||||
sessionStorage.setItem('lastTimestamp', params.timestamp);
|
||||
play(params.messageInfo);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('webOSLaunch', launchHandler);
|
||||
document.addEventListener('webOSRelaunch', launchHandler);
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
visibilityChangedService = callService('visibility_changed', { hidden: document.hidden, window: 'main' }, null, null, () => {
|
||||
visibilityChangedService = null;
|
||||
})
|
||||
});
|
||||
|
||||
// Cannot go back to a state where user was previously casting a video, so exit.
|
||||
// window.onpopstate = () => {
|
||||
// window.webOS.platformBack();
|
||||
// };
|
||||
window.parent.webOSApp.setLaunchHandler(launchHandler);
|
||||
document.addEventListener('visibilitychange', () => { serviceManager.call('visibility_changed', { hidden: document.hidden, window: 'main' }); });
|
||||
|
||||
const play = (messageInfo: any) => {
|
||||
sessionStorage.setItem('playInfo', JSON.stringify(messageInfo));
|
||||
|
||||
getDeviceInfoService?.cancel();
|
||||
onEventSubscribedKeysUpdateService?.cancel();
|
||||
getSessionsService?.cancel();
|
||||
toastService?.cancel();
|
||||
onConnectService?.cancel();
|
||||
onDisconnectService?.cancel();
|
||||
playService?.cancel();
|
||||
networkChangedService?.cancel();
|
||||
visibilityChangedService?.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(`../${messageInfo.contentViewer}/index.html`, '_self');
|
||||
window.parent.webOSApp.loadPage(`${messageInfo.contentViewer}/index.html`);
|
||||
};
|
||||
}
|
||||
catch (err) {
|
||||
logger.error(`Main: preload ${JSON.stringify(err)}`);
|
||||
logger.error(`Main: preload`, err);
|
||||
preloadData.onToastCb(`Error starting the application: ${JSON.stringify(err)}`, ToastIcon.ERROR);
|
||||
}
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
/* WebOS custom player styles */
|
||||
|
||||
html {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-family: InterBold;
|
||||
}
|
||||
|
@ -27,6 +33,14 @@
|
|||
font-family: InterBold;
|
||||
}
|
||||
|
||||
#ips {
|
||||
gap: unset;
|
||||
}
|
||||
|
||||
#ips-iface-icon {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
#window-can-be-closed {
|
||||
font-family: InterRegular;
|
||||
}
|
||||
|
|
101
receivers/webos/fcast-receiver/src/player/1280x720.css
Normal file
101
receivers/webos/fcast-receiver/src/player/1280x720.css
Normal file
|
@ -0,0 +1,101 @@
|
|||
|
||||
/* @media only screen and ((max-width: 1279px) or (max-height: 719px)) { */
|
||||
.card {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
line-height: 18px;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.card-title-separator {
|
||||
margin: 3px 0px;
|
||||
}
|
||||
|
||||
.iconSize {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
#overlay {
|
||||
gap: 10vw;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
#title-text {
|
||||
font-size: 80px;
|
||||
}
|
||||
|
||||
#title-icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
#connection-status {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
#connection-error-icon {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#connection-information-loading-text {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
#scan-to-connect {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#qr-code {
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
margin: 15px auto;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
#ips {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.ip-entry-text {
|
||||
margin-top: 1.5px;
|
||||
margin-bottom: 1.5px;
|
||||
}
|
||||
|
||||
#window-can-be-closed {
|
||||
margin-bottom: 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.lds-ring {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
.lds-ring div {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
#connection-check {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
#toast-notification {
|
||||
padding: 4px;
|
||||
top: -100px;
|
||||
}
|
||||
|
||||
#toast-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
#toast-text {
|
||||
font-size: 18px;
|
||||
}
|
||||
/* } */
|
204
receivers/webos/fcast-receiver/src/player/1920x1080.css
Normal file
204
receivers/webos/fcast-receiver/src/player/1920x1080.css
Normal file
|
@ -0,0 +1,204 @@
|
|||
|
||||
/* @media only screen and ((max-width: 1919px) or (max-height: 1079px)) { */
|
||||
.card {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
line-height: 20px;
|
||||
margin: 5px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.card-title-separator {
|
||||
margin: 3px 0px;
|
||||
}
|
||||
|
||||
.iconSize {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
#overlay {
|
||||
gap: 12.5vw;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
#title-text {
|
||||
font-size: 100px;
|
||||
}
|
||||
|
||||
#title-icon {
|
||||
width: 84px;
|
||||
height: 84px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
#connection-status {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
#connection-error-icon {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#connection-information-loading-text {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
#scan-to-connect {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#qr-code {
|
||||
width: 192px;
|
||||
height: 192px;
|
||||
margin: 15px auto;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
#ips {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.ip-entry-text {
|
||||
margin-top: 4px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
#window-can-be-closed {
|
||||
margin-bottom: 15px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.lds-ring {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
.lds-ring div {
|
||||
width: 84px;
|
||||
height: 84px;
|
||||
}
|
||||
|
||||
#connection-check {
|
||||
width: 84px;
|
||||
height: 84px;
|
||||
margin: 24px;
|
||||
}
|
||||
|
||||
#toast-notification {
|
||||
padding: 8px;
|
||||
top: -140px;
|
||||
}
|
||||
|
||||
#toast-icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
#toast-text {
|
||||
font-size: 20px;
|
||||
}
|
||||
/* } */
|
||||
|
||||
@media only screen and ((max-width: 1279px) or (max-height: 719px)) {
|
||||
.card {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
line-height: 18px;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.card-title-separator {
|
||||
margin: 3px 0px;
|
||||
}
|
||||
|
||||
.iconSize {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
#overlay {
|
||||
gap: 10vw;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
#title-text {
|
||||
font-size: 80px;
|
||||
}
|
||||
|
||||
#title-icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
#connection-status {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
#connection-error-icon {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#connection-information-loading-text {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
#scan-to-connect {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#qr-code {
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
margin: 15px auto;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
#ips {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.ip-entry-text {
|
||||
margin-top: 1.5px;
|
||||
margin-bottom: 1.5px;
|
||||
}
|
||||
|
||||
#window-can-be-closed {
|
||||
margin-bottom: 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.lds-ring {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
.lds-ring div {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
#connection-check {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
#toast-notification {
|
||||
padding: 4px;
|
||||
top: -100px;
|
||||
}
|
||||
|
||||
#toast-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
#toast-text {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
|
@ -2,190 +2,149 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { preloadData } from 'common/player/Preload';
|
||||
import { EventMessage, PlaybackErrorMessage, PlaybackUpdateMessage, PlayMessage, VolumeUpdateMessage } from 'common/Packets';
|
||||
import { callService, requestService } from 'lib/common';
|
||||
import { ServiceManager, initializeWindowSizeStylesheet } from 'lib/common';
|
||||
import { toast, ToastIcon } from 'common/components/Toast';
|
||||
require('lib/webOSTVjs-1.2.10/webOSTV.js');
|
||||
require('lib/webOSTVjs-1.2.10/webOSTV-dev.js');
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
targetAPI: any;
|
||||
webOSAPI: any;
|
||||
webOSApp: any;
|
||||
}
|
||||
}
|
||||
|
||||
const logger = window.targetAPI.logger;
|
||||
const serviceId = 'com.futo.fcast.receiver.service';
|
||||
|
||||
try {
|
||||
let getSessions = null;
|
||||
initializeWindowSizeStylesheet();
|
||||
|
||||
window.webOSAPI = {
|
||||
pendingPlay: JSON.parse(sessionStorage.getItem('playInfo'))
|
||||
};
|
||||
const contentViewer = window.webOSAPI.pendingPlay?.contentViewer;
|
||||
|
||||
const serviceManager: ServiceManager = window.parent.webOSApp.serviceManager;
|
||||
serviceManager.subscribeToServiceChannel((message: any) => {
|
||||
switch (message.event) {
|
||||
case 'toast':
|
||||
preloadData.onToastCb(message.value.message, message.value.icon, message.value.duration);
|
||||
break;
|
||||
|
||||
case 'play': {
|
||||
if (contentViewer !== message.value.contentViewer) {
|
||||
window.parent.webOSApp.loadPage(`${message.value.contentViewer}/index.html`);
|
||||
}
|
||||
else {
|
||||
if (message.value.rendererEvent === 'play-playlist') {
|
||||
if (preloadData.onPlayCb === undefined) {
|
||||
window.webOSAPI.pendingPlay = message.value;
|
||||
}
|
||||
else {
|
||||
preloadData.onPlayPlaylistCb(null, message.value.rendererMessage);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (preloadData.onPlayCb === undefined) {
|
||||
window.webOSAPI.pendingPlay = message.value;
|
||||
}
|
||||
else {
|
||||
preloadData.onPlayCb(null, message.value.rendererMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'pause':
|
||||
preloadData.onPauseCb();
|
||||
break;
|
||||
|
||||
case 'resume':
|
||||
preloadData.onResumeCb();
|
||||
break;
|
||||
|
||||
case 'stop':
|
||||
window.parent.webOSApp.loadPage('main_window/index.html');
|
||||
break;
|
||||
|
||||
case 'seek':
|
||||
preloadData.onSeekCb(null, message.value);
|
||||
break;
|
||||
|
||||
case 'setvolume':
|
||||
preloadData.onSetVolumeCb(null, message.value);
|
||||
break;
|
||||
|
||||
case 'setspeed':
|
||||
preloadData.onSetSpeedCb(null, message.value);
|
||||
break;
|
||||
|
||||
case 'setplaylistitem':
|
||||
preloadData.onSetPlaylistItemCb(null, message.value);
|
||||
break;
|
||||
|
||||
case 'event_subscribed_keys_update':
|
||||
preloadData.onEventSubscribedKeysUpdate(message.value);
|
||||
break;
|
||||
|
||||
case 'connect':
|
||||
preloadData.onConnectCb(null, message.value);
|
||||
break;
|
||||
|
||||
case 'disconnect':
|
||||
preloadData.onDisconnectCb(null, message.value);
|
||||
break;
|
||||
|
||||
// 'play-playlist' is handled in the 'play' message for webOS
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
preloadData.sendPlaybackErrorCb = (error: PlaybackErrorMessage) => {
|
||||
window.webOS.service.request(`luna://${serviceId}/`, {
|
||||
method: 'send_playback_error',
|
||||
parameters: { error },
|
||||
onSuccess: () => {},
|
||||
onFailure: (message: any) => {
|
||||
logger.error(`Player: send_playback_error ${JSON.stringify(message)}`);
|
||||
},
|
||||
});
|
||||
serviceManager.call('send_playback_error', error, null, (message: any) => { logger.error(`Player: send_playback_error ${JSON.stringify(message)}`); });
|
||||
};
|
||||
preloadData.sendPlaybackUpdateCb = (update: PlaybackUpdateMessage) => {
|
||||
window.webOS.service.request(`luna://${serviceId}/`, {
|
||||
method: 'send_playback_update',
|
||||
parameters: { update },
|
||||
// onSuccess: (message: any) => {
|
||||
// logger.info(`Player: send_playback_update ${JSON.stringify(message)}`);
|
||||
// },
|
||||
onSuccess: () => {},
|
||||
onFailure: (message: any) => {
|
||||
logger.error(`Player: send_playback_update ${JSON.stringify(message)}`);
|
||||
},
|
||||
});
|
||||
serviceManager.call('send_playback_update', update, null, (message: any) => { logger.error(`Player: send_playback_update ${JSON.stringify(message)}`); });
|
||||
};
|
||||
preloadData.sendVolumeUpdateCb = (update: VolumeUpdateMessage) => {
|
||||
window.webOS.service.request(`luna://${serviceId}/`, {
|
||||
method: 'send_volume_update',
|
||||
parameters: { update },
|
||||
onSuccess: () => {},
|
||||
onFailure: (message: any) => {
|
||||
logger.error(`Player: send_volume_update ${JSON.stringify(message)}`);
|
||||
},
|
||||
});
|
||||
serviceManager.call('send_volume_update', update, null, (message: any) => { logger.error(`Player: send_volume_update ${JSON.stringify(message)}`); });
|
||||
};
|
||||
preloadData.sendEventCb = (event: EventMessage) => {
|
||||
window.webOS.service.request(`luna://${serviceId}/`, {
|
||||
method: 'send_event',
|
||||
parameters: { event },
|
||||
onSuccess: () => {},
|
||||
onFailure: (message: any) => { logger.error(`Player: send_event ${JSON.stringify(message)}`); },
|
||||
});
|
||||
serviceManager.call('send_event', event, null, (message: any) => { logger.error(`Player: send_event ${JSON.stringify(message)}`); });
|
||||
};
|
||||
|
||||
const playService = requestService('play', (message: any) => {
|
||||
if (contentViewer !== message.value.contentViewer) {
|
||||
playService?.cancel();
|
||||
pauseService?.cancel();
|
||||
resumeService?.cancel();
|
||||
stopService?.cancel();
|
||||
seekService?.cancel();
|
||||
setVolumeService?.cancel();
|
||||
setSpeedService?.cancel();
|
||||
onSetPlaylistItemService?.cancel();
|
||||
getSessions?.cancel();
|
||||
onEventSubscribedKeysUpdateService?.cancel();
|
||||
onConnectService?.cancel();
|
||||
onDisconnectService?.cancel();
|
||||
onPlayPlaylistService?.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(`../${message.value.contentViewer}/index.html`, '_self');
|
||||
}
|
||||
else {
|
||||
if (message.value.rendererEvent === 'play-playlist') {
|
||||
if (preloadData.onPlayCb === undefined) {
|
||||
window.webOSAPI.pendingPlay = message.value;
|
||||
}
|
||||
else {
|
||||
preloadData.onPlayPlaylistCb(null, message.value.rendererMessage);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (preloadData.onPlayCb === undefined) {
|
||||
window.webOSAPI.pendingPlay = message.value;
|
||||
}
|
||||
else {
|
||||
preloadData.onPlayCb(null, message.value.rendererMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, (message: any) => {
|
||||
logger.error(`Player: play ${JSON.stringify(message)}`);
|
||||
});
|
||||
const pauseService = requestService('pause', () => { preloadData.onPauseCb(); });
|
||||
const resumeService = requestService('resume', () => { preloadData.onResumeCb(); });
|
||||
const stopService = requestService('stop', () => {
|
||||
playService?.cancel();
|
||||
pauseService?.cancel();
|
||||
resumeService?.cancel();
|
||||
stopService?.cancel();
|
||||
seekService?.cancel();
|
||||
setVolumeService?.cancel();
|
||||
setSpeedService?.cancel();
|
||||
onSetPlaylistItemService?.cancel();
|
||||
getSessions?.cancel();
|
||||
onEventSubscribedKeysUpdateService?.cancel();
|
||||
onConnectService?.cancel();
|
||||
onDisconnectService?.cancel();
|
||||
onPlayPlaylistService?.cancel();
|
||||
|
||||
// WebOS 22 and earlier does not work well using the history API,
|
||||
// so manually handling page navigation...
|
||||
// history.back();
|
||||
window.open('../main_window/index.html', '_self');
|
||||
});
|
||||
|
||||
const seekService = requestService('seek', (message: any) => { preloadData.onSeekCb(null, message.value); });
|
||||
const setVolumeService = requestService('setvolume', (message: any) => { preloadData.onSetVolumeCb(null, message.value); });
|
||||
const setSpeedService = requestService('setspeed', (message: any) => { preloadData.onSetSpeedCb(null, message.value); });
|
||||
const onSetPlaylistItemService = requestService('setplaylistitem', (message: any) => { preloadData.onSetPlaylistItemCb(null, message.value); });
|
||||
|
||||
preloadData.sendPlayRequestCb = (message: PlayMessage, playlistIndex: number) => {
|
||||
window.webOS.service.request(`luna://${serviceId}/`, {
|
||||
method: 'play_request',
|
||||
parameters: { message: message, playlistIndex: playlistIndex },
|
||||
onSuccess: () => {},
|
||||
onFailure: (message: any) => { logger.error(`Player: play_request ${playlistIndex} ${JSON.stringify(message)}`); },
|
||||
});
|
||||
serviceManager.call('play_request', { message: message, playlistIndex: playlistIndex }, null, (message: any) => { logger.error(`Player: play_request ${playlistIndex} ${JSON.stringify(message)}`); });
|
||||
};
|
||||
window.targetAPI.getSessions(() => {
|
||||
return new Promise((resolve, reject) => {
|
||||
getSessions = callService('get_sessions', {}, (message: any) => resolve(message.value), (message: any) => reject(message));
|
||||
serviceManager.call('get_sessions', {}, (message: any) => resolve(message.value), (message: any) => reject(message));
|
||||
});
|
||||
});
|
||||
|
||||
const onEventSubscribedKeysUpdateService = requestService('event_subscribed_keys_update', (message: any) => { preloadData.onEventSubscribedKeysUpdate(message.value); });
|
||||
const onConnectService = requestService('connect', (message: any) => { preloadData.onConnectCb(null, message.value); });
|
||||
const onDisconnectService = requestService('disconnect', (message: any) => { preloadData.onDisconnectCb(null, message.value); });
|
||||
const onPlayPlaylistService = requestService('play-playlist', (message: any) => { preloadData.onPlayPlaylistCb(null, message.value); });
|
||||
|
||||
const launchHandler = () => {
|
||||
// args don't seem to be passed in via event despite what documentation says...
|
||||
const params = window.webOSDev.launchParams();
|
||||
logger.info(`Player: (Re)launching FCast Receiver with args: ${JSON.stringify(params)}`);
|
||||
|
||||
// WebOS 6.0 and earlier: Timestamp tracking seems to be necessary as launch event is raised regardless if app is in foreground or not
|
||||
const lastTimestamp = Number(localStorage.getItem('lastTimestamp'));
|
||||
const lastTimestamp = Number(sessionStorage.getItem('lastTimestamp'));
|
||||
if (params.messageInfo !== undefined && params.timestamp != lastTimestamp) {
|
||||
localStorage.setItem('lastTimestamp', params.timestamp);
|
||||
sessionStorage.setItem('lastTimestamp', params.timestamp);
|
||||
sessionStorage.setItem('playInfo', JSON.stringify(params.messageInfo));
|
||||
|
||||
playService?.cancel();
|
||||
pauseService?.cancel();
|
||||
resumeService?.cancel();
|
||||
stopService?.cancel();
|
||||
seekService?.cancel();
|
||||
setVolumeService?.cancel();
|
||||
setSpeedService?.cancel();
|
||||
onSetPlaylistItemService?.cancel();
|
||||
getSessions?.cancel();
|
||||
onEventSubscribedKeysUpdateService?.cancel();
|
||||
onConnectService?.cancel();
|
||||
onDisconnectService?.cancel();
|
||||
onPlayPlaylistService?.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(`../${params.messageInfo.contentViewer}/index.html`, '_self');
|
||||
window.parent.webOSApp.loadPage(`${params.messageInfo.contentViewer}/index.html`);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('webOSLaunch', launchHandler);
|
||||
document.addEventListener('webOSRelaunch', launchHandler);
|
||||
document.addEventListener('visibilitychange', () => callService('visibility_changed', { hidden: document.hidden, window: contentViewer }));
|
||||
|
||||
window.parent.webOSApp.setLaunchHandler(launchHandler);
|
||||
document.addEventListener('visibilitychange', () => serviceManager.call('visibility_changed', { hidden: document.hidden, window: contentViewer }));
|
||||
}
|
||||
catch (err) {
|
||||
logger.error(`Player: preload ${JSON.stringify(err)}`);
|
||||
logger.error(`Player: preload`, err);
|
||||
toast(`Error starting the video player (preload): ${JSON.stringify(err)}`, ToastIcon.ERROR);
|
||||
}
|
||||
|
|
101
receivers/webos/fcast-receiver/src/viewer/1280x720.css
Normal file
101
receivers/webos/fcast-receiver/src/viewer/1280x720.css
Normal file
|
@ -0,0 +1,101 @@
|
|||
|
||||
/* @media only screen and ((max-width: 1279px) or (max-height: 719px)) { */
|
||||
.card {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
line-height: 18px;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.card-title-separator {
|
||||
margin: 3px 0px;
|
||||
}
|
||||
|
||||
.iconSize {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
#overlay {
|
||||
gap: 10vw;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
#title-text {
|
||||
font-size: 80px;
|
||||
}
|
||||
|
||||
#title-icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
#connection-status {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
#connection-error-icon {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#connection-information-loading-text {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
#scan-to-connect {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#qr-code {
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
margin: 15px auto;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
#ips {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.ip-entry-text {
|
||||
margin-top: 1.5px;
|
||||
margin-bottom: 1.5px;
|
||||
}
|
||||
|
||||
#window-can-be-closed {
|
||||
margin-bottom: 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.lds-ring {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
.lds-ring div {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
#connection-check {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
#toast-notification {
|
||||
padding: 4px;
|
||||
top: -100px;
|
||||
}
|
||||
|
||||
#toast-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
#toast-text {
|
||||
font-size: 18px;
|
||||
}
|
||||
/* } */
|
204
receivers/webos/fcast-receiver/src/viewer/1920x1080.css
Normal file
204
receivers/webos/fcast-receiver/src/viewer/1920x1080.css
Normal file
|
@ -0,0 +1,204 @@
|
|||
|
||||
/* @media only screen and ((max-width: 1919px) or (max-height: 1079px)) { */
|
||||
.card {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
line-height: 20px;
|
||||
margin: 5px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.card-title-separator {
|
||||
margin: 3px 0px;
|
||||
}
|
||||
|
||||
.iconSize {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
#overlay {
|
||||
gap: 12.5vw;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
#title-text {
|
||||
font-size: 100px;
|
||||
}
|
||||
|
||||
#title-icon {
|
||||
width: 84px;
|
||||
height: 84px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
#connection-status {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
#connection-error-icon {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#connection-information-loading-text {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
#scan-to-connect {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#qr-code {
|
||||
width: 192px;
|
||||
height: 192px;
|
||||
margin: 15px auto;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
#ips {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.ip-entry-text {
|
||||
margin-top: 4px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
#window-can-be-closed {
|
||||
margin-bottom: 15px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.lds-ring {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
.lds-ring div {
|
||||
width: 84px;
|
||||
height: 84px;
|
||||
}
|
||||
|
||||
#connection-check {
|
||||
width: 84px;
|
||||
height: 84px;
|
||||
margin: 24px;
|
||||
}
|
||||
|
||||
#toast-notification {
|
||||
padding: 8px;
|
||||
top: -140px;
|
||||
}
|
||||
|
||||
#toast-icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
#toast-text {
|
||||
font-size: 20px;
|
||||
}
|
||||
/* } */
|
||||
|
||||
@media only screen and ((max-width: 1279px) or (max-height: 719px)) {
|
||||
.card {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
line-height: 18px;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.card-title-separator {
|
||||
margin: 3px 0px;
|
||||
}
|
||||
|
||||
.iconSize {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
#overlay {
|
||||
gap: 10vw;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
#title-text {
|
||||
font-size: 80px;
|
||||
}
|
||||
|
||||
#title-icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
#connection-status {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
#connection-error-icon {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#connection-information-loading-text {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
#scan-to-connect {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#qr-code {
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
margin: 15px auto;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
#ips {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.ip-entry-text {
|
||||
margin-top: 1.5px;
|
||||
margin-bottom: 1.5px;
|
||||
}
|
||||
|
||||
#window-can-be-closed {
|
||||
margin-bottom: 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.lds-ring {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
.lds-ring div {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
#connection-check {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
#toast-notification {
|
||||
padding: 4px;
|
||||
top: -100px;
|
||||
}
|
||||
|
||||
#toast-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
#toast-text {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
|
@ -1 +1,5 @@
|
|||
/* Stub for future use */
|
||||
/* WebOS custom player styles */
|
||||
|
||||
html {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
|
|
@ -12,6 +12,71 @@ const TARGET = 'webOS';
|
|||
// const TARGET = 'tizenOS';
|
||||
|
||||
module.exports = [
|
||||
{
|
||||
mode: buildMode,
|
||||
entry: {
|
||||
main: './src/Main.ts',
|
||||
},
|
||||
target: ['web', 'es5'],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
include: [path.resolve(__dirname, '../../common/web'), path.resolve(__dirname, 'src')],
|
||||
use: [{ loader: 'ts-loader' }]
|
||||
},
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
include: [path.resolve(__dirname, 'lib'), path.resolve(__dirname, 'src')],
|
||||
use: [{ loader: 'ts-loader' }]
|
||||
}
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'src': path.resolve(__dirname, 'src'),
|
||||
'lib': path.resolve(__dirname, 'lib'),
|
||||
'modules': path.resolve(__dirname, 'node_modules'),
|
||||
'common': path.resolve(__dirname, '../../common/web'),
|
||||
},
|
||||
extensions: ['.tsx', '.ts', '.js'],
|
||||
},
|
||||
output: {
|
||||
filename: '[name].js',
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
},
|
||||
plugins: [
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
// Common assets
|
||||
{
|
||||
from: '../common/assets/**',
|
||||
to: '[path][name][ext]',
|
||||
context: path.resolve(__dirname, '..', '..', 'common'),
|
||||
globOptions: { ignore: ['**/*.txt'] }
|
||||
},
|
||||
// Target assets
|
||||
{ from: 'appinfo.json', to: '[name][ext]' },
|
||||
{
|
||||
from: '**',
|
||||
to: 'assets/[path][name][ext]',
|
||||
context: path.resolve(__dirname, 'assets'),
|
||||
globOptions: { ignore: ['**/*.svg'] }
|
||||
},
|
||||
{
|
||||
from: '**',
|
||||
to: 'lib/[name][ext]',
|
||||
context: path.resolve(__dirname, 'lib'),
|
||||
globOptions: { ignore: ['**/*.txt'] }
|
||||
},
|
||||
{ from: './src/index.html', to: '[name][ext]' }
|
||||
],
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
TARGET: JSON.stringify(TARGET)
|
||||
})
|
||||
]
|
||||
},
|
||||
{
|
||||
mode: buildMode,
|
||||
entry: {
|
||||
|
@ -50,31 +115,10 @@ module.exports = [
|
|||
plugins: [
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
// Common assets
|
||||
{
|
||||
from: '../common/assets/**',
|
||||
to: '../[path][name][ext]',
|
||||
context: path.resolve(__dirname, '..', '..', 'common'),
|
||||
globOptions: { ignore: ['**/*.txt'] }
|
||||
},
|
||||
{
|
||||
from: '../../common/web/main/common.css',
|
||||
to: '[name][ext]',
|
||||
},
|
||||
// Target assets
|
||||
{ from: 'appinfo.json', to: '../[name][ext]' },
|
||||
{
|
||||
from: '**',
|
||||
to: '../assets/[path][name][ext]',
|
||||
context: path.resolve(__dirname, 'assets'),
|
||||
globOptions: { ignore: ['**/*.svg'] }
|
||||
},
|
||||
{
|
||||
from: '**',
|
||||
to: '../lib/[name][ext]',
|
||||
context: path.resolve(__dirname, 'lib'),
|
||||
globOptions: { ignore: ['**/*.txt'] }
|
||||
},
|
||||
{
|
||||
from: './src/main/*',
|
||||
to: '[name][ext]',
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue