Using one global video renderer instead of detached HTML elements for each connection
parent
1cae741b17
commit
a0ba132182
|
@ -34,7 +34,7 @@ export class AppController {
|
|||
constructor() {
|
||||
this.uiEvents = new Registry<AppUiEvents>();
|
||||
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 = [];
|
||||
}
|
||||
|
@ -126,8 +126,9 @@ export class AppController {
|
|||
}
|
||||
|
||||
private notifyVideoContainer() {
|
||||
this.uiEvents.fire_react("notify_video_container", {
|
||||
container: this.currentConnection?.video_frame.getContainer()
|
||||
this.uiEvents.fire_react("notify_video", {
|
||||
events: this.currentConnection?.video_frame.getEvents(),
|
||||
handlerId: this.currentConnection?.handlerId
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
import {Registry} from "tc-shared/events";
|
||||
import {ChannelTreeUIEvents} from "tc-shared/ui/tree/Definitions";
|
||||
import {ChannelVideoEvents} from "tc-shared/ui/frames/video/Definitions";
|
||||
|
||||
export interface AppUiEvents {
|
||||
query_channel_tree: {},
|
||||
query_video_container: {},
|
||||
query_video: {},
|
||||
|
||||
notify_channel_tree: {
|
||||
events: Registry<ChannelTreeUIEvents> | undefined,
|
||||
handlerId: string
|
||||
},
|
||||
notify_video_container: {
|
||||
container: HTMLDivElement | undefined
|
||||
notify_video: {
|
||||
events: Registry<ChannelVideoEvents> | undefined,
|
||||
handlerId: string
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
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 {Registry} from "tc-shared/events";
|
||||
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 {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";
|
||||
|
||||
const cssStyle = require("./AppRenderer.scss");
|
||||
const VideoFrame = React.memo((props: { events: Registry<AppUiEvents> }) => {
|
||||
const refElement = React.useRef<HTMLDivElement>();
|
||||
const [ container, setContainer ] = useState<HTMLDivElement | undefined>(() => {
|
||||
props.events.fire("query_video_container");
|
||||
return undefined;
|
||||
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_container", event => setContainer(event.container));
|
||||
props.events.reactUse("notify_video", event => setData({ handlerId: event.handlerId, events: event.events }));
|
||||
|
||||
useEffect(() => {
|
||||
if(!refElement.current || !container) {
|
||||
return;
|
||||
}
|
||||
|
||||
refElement.current.replaceWith(container);
|
||||
return () => container.replaceWith(refElement.current);
|
||||
});
|
||||
|
||||
if(!container) {
|
||||
if(!data.events) {
|
||||
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> }) => {
|
||||
|
|
|
@ -346,8 +346,6 @@ class LocalVideoController extends RemoteClientVideoController {
|
|||
}
|
||||
|
||||
class ChannelVideoController {
|
||||
callbackVisibilityChanged: (visible: boolean) => void;
|
||||
|
||||
private readonly connection: ConnectionHandler;
|
||||
private readonly videoConnection: VideoConnection;
|
||||
private readonly events: Registry<ChannelVideoEvents>;
|
||||
|
@ -710,13 +708,13 @@ class ChannelVideoController {
|
|||
}
|
||||
}
|
||||
|
||||
this.updateVisibility(videoStreamingCount !== 0);
|
||||
if(this.expended) {
|
||||
this.currentSpotlights.forEach(entry => videoIds.remove(entry));
|
||||
}
|
||||
|
||||
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 });
|
||||
}
|
||||
|
||||
private updateVisibility(target: boolean) {
|
||||
if(this.currentlyVisible === target) { return; }
|
||||
|
||||
this.currentlyVisible = target;
|
||||
if(this.callbackVisibilityChanged) {
|
||||
this.callbackVisibilityChanged(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class ChannelVideoFrame {
|
||||
private readonly handle: ConnectionHandler;
|
||||
private readonly events: Registry<ChannelVideoEvents>;
|
||||
private container: HTMLDivElement;
|
||||
private controller: ChannelVideoController;
|
||||
|
||||
constructor(handle: ConnectionHandler) {
|
||||
|
@ -807,38 +795,15 @@ export class ChannelVideoFrame {
|
|||
this.events = new Registry<ChannelVideoEvents>();
|
||||
this.controller = new ChannelVideoController(this.events, handle);
|
||||
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() {
|
||||
this.controller?.destroy();
|
||||
this.controller = undefined;
|
||||
|
||||
if(this.container) {
|
||||
this.container.remove();
|
||||
ReactDOM.unmountComponentAtNode(this.container);
|
||||
|
||||
this.container = undefined;
|
||||
}
|
||||
|
||||
this.events.destroy();
|
||||
}
|
||||
|
||||
getContainer() : HTMLDivElement {
|
||||
return this.container;
|
||||
getEvents() : Registry<ChannelVideoEvents> {
|
||||
return this.events;
|
||||
}
|
||||
}
|
|
@ -82,6 +82,7 @@ export interface ChannelVideoEvents {
|
|||
action_show_viewers: {},
|
||||
|
||||
query_expended: {},
|
||||
query_visible: {},
|
||||
query_videos: {},
|
||||
query_video: { videoId: string },
|
||||
query_video_info: { videoId: string },
|
||||
|
@ -93,7 +94,8 @@ export interface ChannelVideoEvents {
|
|||
|
||||
notify_expended: { expended: boolean },
|
||||
notify_videos: {
|
||||
videoIds: string[]
|
||||
videoIds: string[],
|
||||
videoActiveCount: number,
|
||||
},
|
||||
notify_video: {
|
||||
videoId: string,
|
||||
|
|
|
@ -28,15 +28,6 @@ $small_height: 10em;
|
|||
}
|
||||
}
|
||||
|
||||
.heightProvider {
|
||||
height: 100%; /* the footer size (version etc) */
|
||||
|
||||
.spotlight {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.expended {
|
||||
.panel {
|
||||
height: 100%; /* the footer size (version etc) */
|
||||
|
@ -48,6 +39,16 @@ $small_height: 10em;
|
|||
@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 {
|
||||
|
|
|
@ -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 (
|
||||
<EventContext.Provider value={props.events}>
|
||||
<HandlerIdContext.Provider value={props.handlerId}>
|
||||
<PanelContainer>
|
||||
<VideoSubscribeContextProvider>
|
||||
<VideoBar />
|
||||
<ExpendArrow />
|
||||
<ErrorBoundary>
|
||||
<Spotlight />
|
||||
</ErrorBoundary>
|
||||
</VideoSubscribeContextProvider>
|
||||
</PanelContainer>
|
||||
<VisibilityHandler>
|
||||
<PanelContainer>
|
||||
<VideoSubscribeContextProvider>
|
||||
<VideoBar />
|
||||
<ExpendArrow />
|
||||
<ErrorBoundary>
|
||||
<Spotlight />
|
||||
</ErrorBoundary>
|
||||
</VideoSubscribeContextProvider>
|
||||
</PanelContainer>
|
||||
</VisibilityHandler>
|
||||
</HandlerIdContext.Provider>
|
||||
</EventContext.Provider>
|
||||
);
|
||||
};
|
||||
});
|
Loading…
Reference in New Issue