Rendering the main UI only via IPC

master
WolverinDEV 2021-04-29 22:18:11 +02:00
parent a0ba132182
commit f2ab9800d4
14 changed files with 212 additions and 139 deletions

View File

@ -9,20 +9,22 @@ import {initializeConnectionListController} from "tc-shared/ui/frames/connection
import * as loader from "tc-loader";
import {Stage} from "tc-loader";
import {server_connections} from "tc-shared/ConnectionManager";
import {AppUiEvents} from "tc-shared/ui/AppDefinitions";
import {AppUiEvents, AppUiVariables} from "tc-shared/ui/AppDefinitions";
import {ConnectionHandler} from "tc-shared/ConnectionHandler";
import {SideBarController} from "tc-shared/ui/frames/SideBarController";
import {ServerEventLogController} from "tc-shared/ui/frames/log/Controller";
import {HostBannerController} from "tc-shared/ui/frames/HostBannerController";
import {UiVariableProvider} from "tc-shared/ui/utils/Variable";
import {createIpcUiVariableProvider, IpcUiVariableProvider} from "tc-shared/ui/utils/IpcVariable";
export class AppController {
private uiEvents: Registry<AppUiEvents>;
private listener: (() => void)[];
private currentConnection: ConnectionHandler;
private listenerConnection: (() => void)[];
private variables: IpcUiVariableProvider<AppUiVariables>;
private container: HTMLDivElement;
private controlBarEvents: Registry<ControlBarEvents>;
private connectionListEvents: Registry<ConnectionListUIEvents>;
@ -32,9 +34,21 @@ export class AppController {
private hostBannerController: HostBannerController;
constructor() {
this.uiEvents = new Registry<AppUiEvents>();
this.uiEvents.on("query_channel_tree", () => this.notifyChannelTree());
this.uiEvents.on("query_video", () => this.notifyVideoContainer());
this.variables = createIpcUiVariableProvider();
this.variables.setVariableProvider("connectionList", () => this.connectionListEvents.generateIpcDescription());
this.variables.setVariableProvider("controlBar", () => this.controlBarEvents.generateIpcDescription());
this.variables.setVariableProvider("hostBanner", () => this.hostBannerController.uiEvents.generateIpcDescription());
this.variables.setVariableProvider("log", () => this.serverLogController.events.generateIpcDescription());
this.variables.setVariableProvider("sidebar", () => this.sideBarController.uiEvents.generateIpcDescription());
this.variables.setVariableProvider("sidebarHeader", () => this.sideBarController.getHeaderController().uiEvents.generateIpcDescription());
this.variables.setVariableProvider("channelTree", () => ({
events: this.currentConnection?.channelTree.mainTreeUiEvents.generateIpcDescription(),
handlerId: this.currentConnection?.handlerId
}));
this.variables.setVariableProvider("channelVideo", () => ({
events: this.currentConnection?.video_frame.getEvents().generateIpcDescription(),
handlerId: this.currentConnection?.handlerId
}));
this.listener = [];
}
@ -64,8 +78,8 @@ export class AppController {
this.hostBannerController?.destroy();
this.hostBannerController = undefined;
this.uiEvents?.destroy();
this.uiEvents = undefined;
this.variables?.destroy();
this.variables = undefined;
}
initialize() {
@ -102,35 +116,15 @@ export class AppController {
this.serverLogController.setConnectionHandler(connection);
this.hostBannerController.setConnectionHandler(connection);
this.notifyChannelTree();
this.notifyVideoContainer();
this.variables.sendVariable("channelTree");
this.variables.sendVariable("channelVideo");
}
renderApp() {
ReactDOM.render(React.createElement(TeaAppMainView, {
controlBar: this.controlBarEvents,
connectionList: this.connectionListEvents,
sidebar: this.sideBarController.uiEvents,
sidebarHeader: this.sideBarController.getHeaderController().uiEvents,
log: this.serverLogController.events,
events: this.uiEvents,
hostBanner: this.hostBannerController.uiEvents
variables: this.variables.generateConsumerDescription(),
}), this.container);
}
private notifyChannelTree() {
this.uiEvents.fire_react("notify_channel_tree", {
handlerId: this.currentConnection?.handlerId,
events: this.currentConnection?.channelTree.mainTreeUiEvents
});
}
private notifyVideoContainer() {
this.uiEvents.fire_react("notify_video", {
events: this.currentConnection?.video_frame.getEvents(),
handlerId: this.currentConnection?.handlerId
});
}
}
export let appViewController: AppController;

View File

@ -1,6 +1,29 @@
import {Registry} from "tc-shared/events";
import {IpcRegistryDescription, Registry} from "tc-shared/events";
import {ChannelTreeUIEvents} from "tc-shared/ui/tree/Definitions";
import {ChannelVideoEvents} from "tc-shared/ui/frames/video/Definitions";
import {ControlBarEvents} from "tc-shared/ui/frames/control-bar/Definitions";
import {ConnectionListUIEvents} from "tc-shared/ui/frames/connection-handler-list/Definitions";
import {SideBarEvents} from "tc-shared/ui/frames/SideBarDefinitions";
import {SideHeaderEvents} from "tc-shared/ui/frames/side/HeaderDefinitions";
import {ServerEventLogUiEvents} from "tc-shared/ui/frames/log/Definitions";
import {HostBannerUiEvents} from "tc-shared/ui/frames/HostBannerDefinitions";
export interface AppUiVariables {
readonly channelTree: {
events: IpcRegistryDescription<ChannelTreeUIEvents> | undefined,
handlerId: string
},
readonly channelVideo: {
events: IpcRegistryDescription<ChannelVideoEvents>,
handlerId: string
},
readonly controlBar: IpcRegistryDescription<ControlBarEvents>,
readonly connectionList: IpcRegistryDescription<ConnectionListUIEvents>,
readonly sidebar: IpcRegistryDescription<SideBarEvents>,
readonly sidebarHeader: IpcRegistryDescription<SideHeaderEvents>,
readonly log: IpcRegistryDescription<ServerEventLogUiEvents>,
readonly hostBanner: IpcRegistryDescription<HostBannerUiEvents>,
}
export interface AppUiEvents {
query_channel_tree: {},

View File

@ -1,96 +1,117 @@
import * as React from "react";
import {useState} from "react";
import {useEffect, useMemo} from "react";
import {ControlBar2} from "tc-shared/ui/frames/control-bar/Renderer";
import {Registry} from "tc-shared/events";
import {ControlBarEvents} from "tc-shared/ui/frames/control-bar/Definitions";
import {ConnectionListUIEvents} from "tc-shared/ui/frames/connection-handler-list/Definitions";
import {IpcRegistryDescription, Registry} from "tc-shared/events";
import {ConnectionHandlerList} from "tc-shared/ui/frames/connection-handler-list/Renderer";
import {ErrorBoundary} from "tc-shared/ui/react-elements/ErrorBoundary";
import {ContextDivider} from "tc-shared/ui/react-elements/ContextDivider";
import {SideBarRenderer} from "tc-shared/ui/frames/SideBarRenderer";
import {SideBarEvents} from "tc-shared/ui/frames/SideBarDefinitions";
import {SideHeaderEvents} from "tc-shared/ui/frames/side/HeaderDefinitions";
import {ServerLogFrame} from "tc-shared/ui/frames/log/Renderer";
import {ServerEventLogUiEvents} from "tc-shared/ui/frames/log/Definitions";
import {FooterRenderer} from "tc-shared/ui/frames/footer/Renderer";
import {HostBanner} from "tc-shared/ui/frames/HostBannerRenderer";
import {HostBannerUiEvents} from "tc-shared/ui/frames/HostBannerDefinitions";
import {AppUiEvents} from "tc-shared/ui/AppDefinitions";
import {AppUiVariables} from "tc-shared/ui/AppDefinitions";
import {ChannelTreeRenderer} from "tc-shared/ui/tree/Renderer";
import {ChannelTreeUIEvents} from "tc-shared/ui/tree/Definitions";
import {ImagePreviewHook} from "tc-shared/ui/frames/ImagePreview";
import {InternalModalHook} from "tc-shared/ui/react-elements/modal/internal";
import {TooltipHook} from "tc-shared/ui/react-elements/Tooltip";
import {ChannelVideoEvents} from "tc-shared/ui/frames/video/Definitions";
import {ChannelVideoRenderer} from "tc-shared/ui/frames/video/Renderer";
import {
createIpcUiVariableConsumer,
IpcVariableDescriptor
} from "tc-shared/ui/utils/IpcVariable";
import {UiVariableConsumer} from "tc-shared/ui/utils/Variable";
import {useRegistry} from "tc-shared/ui/react-elements/Helper";
const cssStyle = require("./AppRenderer.scss");
const VideoFrame = React.memo((props: { events: Registry<AppUiEvents> }) => {
const [ data, setData ] = useState<{ events: Registry<ChannelVideoEvents> | undefined, handlerId: string | undefined }>(() => {
props.events.fire("query_video");
return { events: undefined, handlerId: undefined };
});
props.events.reactUse("notify_video", event => setData({ handlerId: event.handlerId, events: event.events }));
const VideoFrame = React.memo((props: { variables: UiVariableConsumer<AppUiVariables> }) => {
const data = props.variables.useReadOnly("channelVideo", undefined, { handlerId: undefined, events: undefined });
const events = useRegistry(data.events);
if(!data.events) {
return null;
}
return <ChannelVideoRenderer handlerId={data.handlerId} events={data.events} key={"video-" + data.handlerId} />;
return <ChannelVideoRenderer handlerId={data.handlerId} events={events} key={"video-" + data.handlerId} />;
});
const ChannelTree = React.memo((props: { events: Registry<AppUiEvents> }) => {
const [ data, setData ] = useState<{ events: Registry<ChannelTreeUIEvents>, handlerId: string }>(() => {
props.events.fire("query_channel_tree");
return undefined;
});
const ChannelTree = React.memo((props: { variables: UiVariableConsumer<AppUiVariables> }) => {
const data = props.variables.useReadOnly("channelTree", undefined, { handlerId: undefined, events: undefined });
const events = useRegistry(data.events);
props.events.reactUse("notify_channel_tree", event => {
setData({ events: event.events, handlerId: event.handlerId });
}, undefined, []);
if(!data?.events) {
if(!events) {
return null;
}
return <ChannelTreeRenderer handlerId={data.handlerId} events={data.events} />;
return <ChannelTreeRenderer handlerId={data.handlerId} events={events} key={"tree-" + data.handlerId} />;
});
const SideBar = React.memo((props: { variables: UiVariableConsumer<AppUiVariables> }) => (
<EventProvider registry={props.variables.useReadOnly("sidebar", undefined, undefined)}>
{sidebarRegistry => (
<EventProvider registry={props.variables.useReadOnly("sidebarHeader", undefined, undefined)}>
{sidebarHeaderRegistry => sidebarRegistry && sidebarHeaderRegistry ? (
<SideBarRenderer events={sidebarRegistry} eventsHeader={sidebarHeaderRegistry} className={cssStyle.sideBar} />
) : null}
</EventProvider>
)}
</EventProvider>
));
function EventProvider<T>(props: {
registry: IpcRegistryDescription<T> | undefined,
children: (registry: Registry<T> | undefined) => React.ReactElement
}) : React.ReactElement {
return props.children(useRegistry(props.registry));
}
export const TeaAppMainView = (props: {
events: Registry<AppUiEvents>
controlBar: Registry<ControlBarEvents>,
connectionList: Registry<ConnectionListUIEvents>,
sidebar: Registry<SideBarEvents>,
sidebarHeader: Registry<SideHeaderEvents>,
log: Registry<ServerEventLogUiEvents>,
hostBanner: Registry<HostBannerUiEvents>
variables: IpcVariableDescriptor<AppUiVariables>,
}) => {
const variables = useMemo(() => createIpcUiVariableConsumer(props.variables), [ props.variables ]);
useEffect(() => () => variables.destroy(), [ props.variables ]);
return (
<div className={cssStyle.app}>
<ErrorBoundary>
<ControlBar2 events={props.controlBar} className={cssStyle.controlBar} />
<EventProvider registry={variables.useReadOnly("controlBar", undefined, undefined)}>
{registry => registry ? (
<ControlBar2 events={registry} className={cssStyle.controlBar} />
) : null}
</EventProvider>
</ErrorBoundary>
<ErrorBoundary>
<ConnectionHandlerList events={props.connectionList} />
<EventProvider registry={variables.useReadOnly("connectionList", undefined, undefined)}>
{registry => registry ? (
<ConnectionHandlerList events={registry} />
) : null}
</EventProvider>
</ErrorBoundary>
<div className={cssStyle.mainContainer}>
<VideoFrame events={props.events} />
<VideoFrame variables={variables} />
<div className={cssStyle.channelTreeAndSidebar}>
<div className={cssStyle.channelTree}>
<ErrorBoundary>
<HostBanner events={props.hostBanner} />
<ChannelTree events={props.events} />
<EventProvider registry={variables.useReadOnly("hostBanner", undefined, undefined)}>
{registry => registry ? (
<HostBanner events={registry} />
) : null}
</EventProvider>
<ChannelTree variables={variables} />
</ErrorBoundary>
</div>
<ContextDivider id={"channel-chat"} direction={"horizontal"} defaultValue={25} />
<SideBarRenderer events={props.sidebar} eventsHeader={props.sidebarHeader} className={cssStyle.sideBar} />
<SideBar variables={variables} />
</div>
<ContextDivider id={"main-log"} direction={"vertical"} defaultValue={75} />
<ErrorBoundary>
<div className={cssStyle.containerLog}>
<ServerLogFrame events={props.log} />
<EventProvider registry={variables.useReadOnly("log", undefined, undefined)}>
{registry => registry ? (
<ServerLogFrame events={registry} />
) : null}
</EventProvider>
</div>
</ErrorBoundary>
</div>

View File

@ -73,15 +73,6 @@ export class SideBarController {
this.privateConversations = undefined;
}
renderInto(container: HTMLDivElement) {
/*
ReactDOM.render(React.createElement(SideBarRenderer, {
events: this.uiEvents,
eventsHeader: this.header["uiEvents"],
}), container);
*/
}
getMusicController() : MusicBotController {
return this.musicPanel;
}
@ -116,7 +107,7 @@ export class SideBarController {
this.uiEvents.fire_react("notify_content_data", {
content: "channel",
data: {
events: this.channelBar.uiEvents,
events: this.channelBar.uiEvents.generateIpcDescription(),
}
});
break;
@ -125,7 +116,7 @@ export class SideBarController {
this.uiEvents.fire_react("notify_content_data", {
content: "server",
data: this.currentConnection ? {
chatEvents: this.channelBar.getChannelConversationController().getUiEvents(),
chatEvents: this.channelBar.getChannelConversationController().getUiEvents().generateIpcDescription(),
handlerId: this.currentConnection.handlerId
} : undefined
});
@ -140,7 +131,7 @@ export class SideBarController {
this.uiEvents.fire_react("notify_content_data", {
content: "private-chat",
data: {
events: this.privateConversations["uiEvents"],
events: this.privateConversations["uiEvents"].generateIpcDescription(),
handlerId: this.currentConnection.handlerId
}
});
@ -155,7 +146,7 @@ export class SideBarController {
this.uiEvents.fire_react("notify_content_data", {
content: "client-info",
data: {
events: this.clientInfo["uiEvents"],
events: this.clientInfo["uiEvents"].generateIpcDescription(),
}
});
break;
@ -169,8 +160,8 @@ export class SideBarController {
this.uiEvents.fire_react("notify_content_data", {
content: "music-manage",
data: {
botEvents: this.musicPanel.getBotUiEvents(),
playlistEvents: this.musicPanel.getPlaylistUiEvents()
botEvents: this.musicPanel.getBotUiEvents().generateIpcDescription(),
playlistEvents: this.musicPanel.getPlaylistUiEvents().generateIpcDescription()
}
});
break;

View File

@ -1,4 +1,4 @@
import {Registry} from "tc-shared/events";
import {IpcRegistryDescription, Registry} from "tc-shared/events";
import {PrivateConversationUIEvents} from "tc-shared/ui/frames/side/PrivateConversationDefinitions";
import {ClientInfoEvents} from "tc-shared/ui/frames/side/ClientInfoDefinitions";
import {SideHeaderEvents} from "tc-shared/ui/frames/side/HeaderDefinitions";
@ -7,28 +7,26 @@ import {MusicBotUiEvents} from "tc-shared/ui/frames/side/MusicBotDefinitions";
import {MusicPlaylistUiEvents} from "tc-shared/ui/frames/side/MusicPlaylistDefinitions";
import {ChannelConversationUiEvents} from "tc-shared/ui/frames/side/ChannelConversationDefinitions";
/* TODO: Somehow outsource the event registries to IPC? */
export type SideBarType = "none" | "server" | "channel" | "private-chat" | "client-info" | "music-manage";
export interface SideBarTypeData {
"none": {},
"channel": {
events: Registry<ChannelBarUiEvents>
events: IpcRegistryDescription<ChannelBarUiEvents>
},
"private-chat": {
events: Registry<PrivateConversationUIEvents>,
events: IpcRegistryDescription<PrivateConversationUIEvents>,
handlerId: string
},
"client-info": {
events: Registry<ClientInfoEvents>,
events: IpcRegistryDescription<ClientInfoEvents>,
},
"music-manage": {
botEvents: Registry<MusicBotUiEvents>,
playlistEvents: Registry<MusicPlaylistUiEvents>
botEvents: IpcRegistryDescription<MusicBotUiEvents>,
playlistEvents: IpcRegistryDescription<MusicPlaylistUiEvents>
},
"server": {
handlerId: string,
chatEvents: Registry<ChannelConversationUiEvents>
chatEvents: IpcRegistryDescription<ChannelConversationUiEvents>
}
}
@ -40,9 +38,7 @@ export type SideBarNotifyContentData<T extends SideBarType> = {
export interface SideBarEvents {
query_content: {},
query_content_data: { content: SideBarType },
query_header_data: {},
notify_content: { content: SideBarType },
notify_content_data: SideBarNotifyContentData<SideBarType>,
notify_header_data: { events: Registry<SideHeaderEvents> }
}

View File

@ -11,6 +11,7 @@ import {ErrorBoundary} from "tc-shared/ui/react-elements/ErrorBoundary";
import {MusicBotRenderer} from "tc-shared/ui/frames/side/MusicBotRenderer";
import {ConversationPanel} from "tc-shared/ui/frames/side/AbstractConversationRenderer";
import React = require("react");
import {useRegistry} from "tc-shared/ui/react-elements/Helper";
const cssStyle = require("./SideBarRenderer.scss");
@ -29,24 +30,28 @@ function useContentData<T extends SideBarType>(type: T) : SideBarTypeData[T] {
const ContentRendererChannel = () => {
const contentData = useContentData("channel");
if(!contentData) { return null; }
const events = useRegistry(contentData?.events);
if(!contentData || !events) { return null; }
return (
<ChannelBarRenderer
key={"channel"}
events={contentData.events}
events={events}
/>
);
};
const ContentRendererServer = () => {
const contentData = useContentData("server");
if(!contentData) { return null; }
const events = useRegistry(contentData?.chatEvents);
if(!contentData || !events) { return null; }
return (
<ConversationPanel
key={"server"}
events={contentData.chatEvents}
events={events}
handlerId={contentData.handlerId}
messagesDeletable={true}
noFirstMessageOverlay={false}
@ -57,11 +62,13 @@ const ContentRendererServer = () => {
const ContentRendererPrivateConversation = () => {
const contentData = useContentData("private-chat");
if(!contentData) { return null; }
const events = useRegistry(contentData?.events);
if(!contentData || !events) { return null; }
return (
<PrivateConversationsPanel
events={contentData.events}
events={events}
handlerId={contentData.handlerId}
/>
);
@ -69,23 +76,26 @@ const ContentRendererPrivateConversation = () => {
const ContentRendererClientInfo = () => {
const contentData = useContentData("client-info");
if(!contentData) { return null; }
const events = useRegistry(contentData?.events);
if(!contentData || !events) { return null; }
return (
<ClientInfoRenderer
events={contentData.events}
events={events}
/>
);
};
const ContentRendererMusicManage = () => {
const contentData = useContentData("music-manage");
if(!contentData) { return null; }
const botEvents = useRegistry(contentData?.botEvents);
const playlistEvents = useRegistry(contentData?.playlistEvents);
if(!contentData || !botEvents || !playlistEvents) { return null; }
return (
<MusicBotRenderer
botEvents={contentData.botEvents}
playlistEvents={contentData.playlistEvents}
botEvents={botEvents}
playlistEvents={playlistEvents}
/>
);
};

View File

@ -181,7 +181,7 @@ export class ChannelBarController {
this.uiEvents.fire_react("notify_data", {
content: "conversation",
data: {
events: this.channelConversations.getUiEvents()
events: this.channelConversations.getUiEvents().generateIpcDescription()
}
});
break;
@ -190,7 +190,7 @@ export class ChannelBarController {
this.uiEvents.fire_react("notify_data", {
content: "description",
data: {
events: this.description.uiEvents
events: this.description.uiEvents.generateIpcDescription()
}
});
break;
@ -199,7 +199,7 @@ export class ChannelBarController {
this.uiEvents.fire_react("notify_data", {
content: "file-transfer",
data: {
events: this.fileBrowser.uiEvents
events: this.fileBrowser.uiEvents.generateIpcDescription()
}
});
break;

View File

@ -1,4 +1,4 @@
import {Registry} from "tc-shared/events";
import {IpcRegistryDescription} from "tc-shared/events";
import {ChannelConversationUiEvents} from "tc-shared/ui/frames/side/ChannelConversationDefinitions";
import {ChannelDescriptionUiEvents} from "tc-shared/ui/frames/side/ChannelDescriptionDefinitions";
import {ChannelFileBrowserUiEvents} from "tc-shared/ui/frames/side/ChannelFileBrowserDefinitions";
@ -7,13 +7,13 @@ export type ChannelBarMode = "conversation" | "description" | "file-transfer" |
export interface ChannelBarModeData {
"conversation": {
events: Registry<ChannelConversationUiEvents>,
events: IpcRegistryDescription<ChannelConversationUiEvents>,
},
"description": {
events: Registry<ChannelDescriptionUiEvents>
events: IpcRegistryDescription<ChannelDescriptionUiEvents>
},
"file-transfer": {
events: Registry<ChannelFileBrowserUiEvents>
events: IpcRegistryDescription<ChannelFileBrowserUiEvents>
},
"none": {}
}

View File

@ -3,7 +3,7 @@ import {ChannelBarMode, ChannelBarModeData, ChannelBarUiEvents} from "tc-shared/
import * as React from "react";
import {useContext, useState} from "react";
import {ConversationPanel} from "tc-shared/ui/frames/side/AbstractConversationRenderer";
import {useDependentState} from "tc-shared/ui/react-elements/Helper";
import {useDependentState, useRegistry} from "tc-shared/ui/react-elements/Helper";
import {ChannelDescriptionRenderer} from "tc-shared/ui/frames/side/ChannelDescriptionRenderer";
import {ChannelFileBrowser} from "tc-shared/ui/frames/side/ChannelFileBrowserRenderer";
@ -49,12 +49,13 @@ const ModeRenderer = () => {
const ModeRendererConversation = React.memo(() => {
const channelContext = useContext(ChannelContext);
const data = useModeData("conversation", [ channelContext ]);
if(!data) { return null; }
const events = useRegistry(data?.events);
if(!data || !events) { return null; }
return (
<ConversationPanel
key={"conversation"}
events={data.events}
events={events}
handlerId={channelContext.handlerId}
messagesDeletable={true}
noFirstMessageOverlay={false}
@ -66,20 +67,22 @@ const ModeRendererConversation = React.memo(() => {
const ModeRendererDescription = React.memo(() => {
const channelContext = useContext(ChannelContext);
const data = useModeData("description", [ channelContext ]);
if(!data) { return null; }
const events = useRegistry(data?.events);
if(!data || !events) { return null; }
return (
<ChannelDescriptionRenderer events={data.events} />
<ChannelDescriptionRenderer events={events} />
);
});
const ModeRendererFileTransfer = React.memo(() => {
const channelContext = useContext(ChannelContext);
const data = useModeData("file-transfer", [ channelContext ]);
if(!data) { return null; }
const events = useRegistry(data?.events);
if(!data || !events) { return null; }
return (
<ChannelFileBrowser events={data.events} />
<ChannelFileBrowser events={events} />
);
});

View File

@ -1,11 +1,8 @@
import {ConnectionHandler} from "tc-shared/ConnectionHandler";
import * as React from "react";
import * as ReactDOM from "react-dom";
import {ChannelVideoRenderer} from "tc-shared/ui/frames/video/Renderer";
import {Registry} from "tc-shared/events";
import {
ChannelVideoEvents,
ChannelVideoStreamState,
ChannelVideoStreamState, getVideoStreamMap,
kLocalVideoId,
VideoStreamState
} from "tc-shared/ui/frames/video/Definitions";
@ -24,8 +21,7 @@ import * as _ from "lodash";
import PermissionType from "tc-shared/permission/PermissionType";
import {createErrorModal} from "tc-shared/ui/elements/Modal";
import {spawnVideoViewerInfo} from "tc-shared/ui/modal/video-viewers/Controller";
const cssStyle = require("./Renderer.scss");
import {guid} from "tc-shared/crypto/uid";
let videoIdIndex = 0;
interface ClientVideoController {
@ -234,7 +230,12 @@ class RemoteClientVideoController implements ClientVideoController {
if(!stream) {
state = { state: "failed", reason: tr("Missing video stream") };
} else {
state = { state: "connected", stream: stream };
const streamUuid = guid();
const streamMap = getVideoStreamMap();
streamMap[streamUuid] = stream;
setTimeout(() => delete streamMap[streamUuid], 60 * 1000);
state = { state: "connected", streamObjectId: streamUuid };
}
}

View File

@ -48,7 +48,7 @@ export type VideoStreamState = {
reason?: string
} | {
state: "connected",
stream: MediaStream
streamObjectId: string
};
export type VideoSubscribeInfo = {
@ -173,4 +173,15 @@ export function makeVideoAutoplay(video: HTMLVideoElement) : () => void {
video.removeEventListener("pause", listenerPause);
video.removeEventListener("ended", listenerEnded);
};
}
const kVideoStreamMapName = "__teaspeak_video_streams__" + __build.timestamp + "_" + __build.version;
export function getVideoStreamMap() : { [key: string]: MediaStream } {
let windowInstance = window;
while(windowInstance.opener) {
/* Are we sure about this while loop? */
windowInstance = windowInstance.opener;
}
return windowInstance[kVideoStreamMapName] || (windowInstance[kVideoStreamMapName] = {});
}

View File

@ -5,7 +5,7 @@ import {ClientIcon} from "svg-sprites/client-icons";
import {Registry} from "tc-shared/events";
import {
ChannelVideoEvents, ChannelVideoInfo,
ChannelVideoStreamState,
ChannelVideoStreamState, getVideoStreamMap,
kLocalVideoId, makeVideoAutoplay,
VideoStreamState,
VideoSubscribeInfo
@ -269,7 +269,7 @@ const MediaStreamVideoRenderer = React.memo((props: { stream: MediaStream | unde
)
});
const VideoStreamPlayer = (props: { videoId: string, streamType: VideoBroadcastType, className?: string }) => {
const VideoStreamPlayer = React.memo((props: { videoId: string, streamType: VideoBroadcastType, className?: string }) => {
const events = useContext(EventContext);
const [ state, setState ] = useState<VideoStreamState>(() => {
events.fire("query_video_stream", { videoId: props.videoId, broadcastType: props.streamType });
@ -301,9 +301,20 @@ const VideoStreamPlayer = (props: { videoId: string, streamType: VideoBroadcastT
);
case "connected":
const streamMap = getVideoStreamMap();
if(typeof streamMap[state.streamObjectId] === "undefined") {
return (
<div className={cssStyle.text} key={"missing-stream-object"}>
<div>
<Translatable>Missing stream object</Translatable>
</div>
</div>
);
}
return (
<MediaStreamVideoRenderer
stream={state.stream}
stream={streamMap[state.streamObjectId]}
className={props.className}
title={props.streamType === "camera" ? tr("Camera") : tr("Screen")}
key={"connected"}
@ -324,7 +335,7 @@ const VideoStreamPlayer = (props: { videoId: string, streamType: VideoBroadcastT
</div>
);
}
}
});
const VideoPlayer = React.memo((props: { videoId: string, cameraState: ChannelVideoStreamState, screenState: ChannelVideoStreamState }) => {
const streamElements = [];

View File

@ -1,5 +1,6 @@
import {Dispatch, SetStateAction, useEffect, useMemo, useState} from "react";
import {RegistryKey, RegistryValueType, settings, ValuedRegistryKey} from "tc-shared/settings";
import {IpcRegistryDescription, Registry} from "tc-events";
export function useDependentState<S>(
factory: (prevState?: S) => S,
@ -42,4 +43,12 @@ export function useGlobalSetting(key, defaultValue) {
useEffect(() => settings.globalChangeListener(key, value => setValue(value)), []);
return value;
}
export function useRegistry<T>(description: IpcRegistryDescription<T> | undefined) : Registry<T> | undefined;
export function useRegistry<T>(description: IpcRegistryDescription<T>) : Registry<T>
export function useRegistry(description) {
const events = useMemo(() => description ? Registry.fromIpcDescription(description) : undefined, [ description ]);
useEffect(() => () => events?.destroy(), [ description ]);
return events;
}

View File

@ -150,6 +150,9 @@ export class WebWindowManager implements WindowManager {
});
const features = {
/* TODO: Configureable and enabled by default! */
noopener: "no",
status: "no",
location: "no",
toolbar: "no",