Using one global video renderer instead of detached HTML elements for each connection

master
WolverinDEV 2021-04-29 20:12:50 +02:00
parent 1cae741b17
commit a0ba132182
7 changed files with 70 additions and 83 deletions

View File

@ -34,7 +34,7 @@ export class AppController {
constructor() { constructor() {
this.uiEvents = new Registry<AppUiEvents>(); this.uiEvents = new Registry<AppUiEvents>();
this.uiEvents.on("query_channel_tree", () => this.notifyChannelTree()); this.uiEvents.on("query_channel_tree", () => this.notifyChannelTree());
this.uiEvents.on("query_video_container", () => this.notifyVideoContainer()); this.uiEvents.on("query_video", () => this.notifyVideoContainer());
this.listener = []; this.listener = [];
} }
@ -126,8 +126,9 @@ export class AppController {
} }
private notifyVideoContainer() { private notifyVideoContainer() {
this.uiEvents.fire_react("notify_video_container", { this.uiEvents.fire_react("notify_video", {
container: this.currentConnection?.video_frame.getContainer() events: this.currentConnection?.video_frame.getEvents(),
handlerId: this.currentConnection?.handlerId
}); });
} }
} }

View File

@ -1,15 +1,17 @@
import {Registry} from "tc-shared/events"; import {Registry} from "tc-shared/events";
import {ChannelTreeUIEvents} from "tc-shared/ui/tree/Definitions"; import {ChannelTreeUIEvents} from "tc-shared/ui/tree/Definitions";
import {ChannelVideoEvents} from "tc-shared/ui/frames/video/Definitions";
export interface AppUiEvents { export interface AppUiEvents {
query_channel_tree: {}, query_channel_tree: {},
query_video_container: {}, query_video: {},
notify_channel_tree: { notify_channel_tree: {
events: Registry<ChannelTreeUIEvents> | undefined, events: Registry<ChannelTreeUIEvents> | undefined,
handlerId: string handlerId: string
}, },
notify_video_container: { notify_video: {
container: HTMLDivElement | undefined events: Registry<ChannelVideoEvents> | undefined,
handlerId: string
} }
} }

View File

@ -1,5 +1,5 @@
import * as React from "react"; import * as React from "react";
import {useEffect, useState} from "react"; import {useState} from "react";
import {ControlBar2} from "tc-shared/ui/frames/control-bar/Renderer"; import {ControlBar2} from "tc-shared/ui/frames/control-bar/Renderer";
import {Registry} from "tc-shared/events"; import {Registry} from "tc-shared/events";
import {ControlBarEvents} from "tc-shared/ui/frames/control-bar/Definitions"; import {ControlBarEvents} from "tc-shared/ui/frames/control-bar/Definitions";
@ -21,30 +21,22 @@ import {ChannelTreeUIEvents} from "tc-shared/ui/tree/Definitions";
import {ImagePreviewHook} from "tc-shared/ui/frames/ImagePreview"; import {ImagePreviewHook} from "tc-shared/ui/frames/ImagePreview";
import {InternalModalHook} from "tc-shared/ui/react-elements/modal/internal"; import {InternalModalHook} from "tc-shared/ui/react-elements/modal/internal";
import {TooltipHook} from "tc-shared/ui/react-elements/Tooltip"; 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";
const cssStyle = require("./AppRenderer.scss"); const cssStyle = require("./AppRenderer.scss");
const VideoFrame = React.memo((props: { events: Registry<AppUiEvents> }) => { const VideoFrame = React.memo((props: { events: Registry<AppUiEvents> }) => {
const refElement = React.useRef<HTMLDivElement>(); const [ data, setData ] = useState<{ events: Registry<ChannelVideoEvents> | undefined, handlerId: string | undefined }>(() => {
const [ container, setContainer ] = useState<HTMLDivElement | undefined>(() => { props.events.fire("query_video");
props.events.fire("query_video_container"); return { events: undefined, handlerId: undefined };
return undefined;
}); });
props.events.reactUse("notify_video_container", event => setContainer(event.container)); props.events.reactUse("notify_video", event => setData({ handlerId: event.handlerId, events: event.events }));
useEffect(() => { if(!data.events) {
if(!refElement.current || !container) {
return;
}
refElement.current.replaceWith(container);
return () => container.replaceWith(refElement.current);
});
if(!container) {
return null; return null;
} }
return <div ref={refElement} />; return <ChannelVideoRenderer handlerId={data.handlerId} events={data.events} key={"video-" + data.handlerId} />;
}); });
const ChannelTree = React.memo((props: { events: Registry<AppUiEvents> }) => { const ChannelTree = React.memo((props: { events: Registry<AppUiEvents> }) => {

View File

@ -346,8 +346,6 @@ class LocalVideoController extends RemoteClientVideoController {
} }
class ChannelVideoController { class ChannelVideoController {
callbackVisibilityChanged: (visible: boolean) => void;
private readonly connection: ConnectionHandler; private readonly connection: ConnectionHandler;
private readonly videoConnection: VideoConnection; private readonly videoConnection: VideoConnection;
private readonly events: Registry<ChannelVideoEvents>; private readonly events: Registry<ChannelVideoEvents>;
@ -710,13 +708,13 @@ class ChannelVideoController {
} }
} }
this.updateVisibility(videoStreamingCount !== 0);
if(this.expended) { if(this.expended) {
this.currentSpotlights.forEach(entry => videoIds.remove(entry)); this.currentSpotlights.forEach(entry => videoIds.remove(entry));
} }
this.events.fire_react("notify_videos", { this.events.fire_react("notify_videos", {
videoIds: videoIds videoIds: videoIds,
videoActiveCount: videoStreamingCount
}); });
} }
@ -785,21 +783,11 @@ class ChannelVideoController {
} }
this.events.fire_react("notify_viewer_count", { camera: cameraViewers, screen: screenViewers }); this.events.fire_react("notify_viewer_count", { camera: cameraViewers, screen: screenViewers });
} }
private updateVisibility(target: boolean) {
if(this.currentlyVisible === target) { return; }
this.currentlyVisible = target;
if(this.callbackVisibilityChanged) {
this.callbackVisibilityChanged(target);
}
}
} }
export class ChannelVideoFrame { export class ChannelVideoFrame {
private readonly handle: ConnectionHandler; private readonly handle: ConnectionHandler;
private readonly events: Registry<ChannelVideoEvents>; private readonly events: Registry<ChannelVideoEvents>;
private container: HTMLDivElement;
private controller: ChannelVideoController; private controller: ChannelVideoController;
constructor(handle: ConnectionHandler) { constructor(handle: ConnectionHandler) {
@ -807,38 +795,15 @@ export class ChannelVideoFrame {
this.events = new Registry<ChannelVideoEvents>(); this.events = new Registry<ChannelVideoEvents>();
this.controller = new ChannelVideoController(this.events, handle); this.controller = new ChannelVideoController(this.events, handle);
this.controller.initialize(); this.controller.initialize();
this.container = document.createElement("div");
this.container.classList.add(cssStyle.container, cssStyle.hidden);
ReactDOM.render(React.createElement(ChannelVideoRenderer, { handlerId: handle.handlerId, events: this.events }), this.container);
this.events.on("notify_expended", event => {
this.container.classList.toggle(cssStyle.expended, event.expended);
});
this.controller.callbackVisibilityChanged = flag => {
this.container.classList.toggle(cssStyle.hidden, !flag);
if(!flag) {
this.events.fire("action_toggle_expended", { expended: false })
}
};
} }
destroy() { destroy() {
this.controller?.destroy(); this.controller?.destroy();
this.controller = undefined; this.controller = undefined;
if(this.container) {
this.container.remove();
ReactDOM.unmountComponentAtNode(this.container);
this.container = undefined;
}
this.events.destroy(); this.events.destroy();
} }
getContainer() : HTMLDivElement { getEvents() : Registry<ChannelVideoEvents> {
return this.container; return this.events;
} }
} }

View File

@ -82,6 +82,7 @@ export interface ChannelVideoEvents {
action_show_viewers: {}, action_show_viewers: {},
query_expended: {}, query_expended: {},
query_visible: {},
query_videos: {}, query_videos: {},
query_video: { videoId: string }, query_video: { videoId: string },
query_video_info: { videoId: string }, query_video_info: { videoId: string },
@ -93,7 +94,8 @@ export interface ChannelVideoEvents {
notify_expended: { expended: boolean }, notify_expended: { expended: boolean },
notify_videos: { notify_videos: {
videoIds: string[] videoIds: string[],
videoActiveCount: number,
}, },
notify_video: { notify_video: {
videoId: string, videoId: string,

View File

@ -28,15 +28,6 @@ $small_height: 10em;
} }
} }
.heightProvider {
height: 100%; /* the footer size (version etc) */
.spotlight {
margin-left: 0;
margin-right: 0;
}
}
&.expended { &.expended {
.panel { .panel {
height: 100%; /* the footer size (version etc) */ height: 100%; /* the footer size (version etc) */
@ -48,6 +39,16 @@ $small_height: 10em;
@include transform(rotate(90deg)!important); @include transform(rotate(90deg)!important);
} }
} }
/* Needs to be within the .container class else dosn't work */
.heightProvider {
height: 100%; /* the footer size (version etc) */
.spotlight {
margin-left: 0;
margin-right: 0;
}
}
} }
.panel { .panel {

View File

@ -752,20 +752,44 @@ const PanelContainer = (props: { children }) => {
); );
} }
export const ChannelVideoRenderer = (props: { handlerId: string, events: Registry<ChannelVideoEvents> }) => { const VisibilityHandler = React.memo((props: {
children
}) => {
const events = useContext(EventContext);
const [ streamingCount, setStreamingCount ] = useState<number>(() => {
events.fire("query_videos");
return 0;
});
const [ expanded, setExpanded ] = useState<boolean>(() => {
events.fire("query_expended");
return false;
})
events.reactUse("notify_videos", event => setStreamingCount(event.videoActiveCount));
events.reactUse("notify_expended", event => setExpanded(event.expended));
return (
<div className={joinClassList(cssStyle.container, streamingCount === 0 && cssStyle.hidden, expanded && cssStyle.expended)}>
{props.children}
</div>
)
});
export const ChannelVideoRenderer = React.memo((props: { handlerId: string, events: Registry<ChannelVideoEvents> }) => {
return ( return (
<EventContext.Provider value={props.events}> <EventContext.Provider value={props.events}>
<HandlerIdContext.Provider value={props.handlerId}> <HandlerIdContext.Provider value={props.handlerId}>
<PanelContainer> <VisibilityHandler>
<VideoSubscribeContextProvider> <PanelContainer>
<VideoBar /> <VideoSubscribeContextProvider>
<ExpendArrow /> <VideoBar />
<ErrorBoundary> <ExpendArrow />
<Spotlight /> <ErrorBoundary>
</ErrorBoundary> <Spotlight />
</VideoSubscribeContextProvider> </ErrorBoundary>
</PanelContainer> </VideoSubscribeContextProvider>
</PanelContainer>
</VisibilityHandler>
</HandlerIdContext.Provider> </HandlerIdContext.Provider>
</EventContext.Provider> </EventContext.Provider>
); );
}; });