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() {
|
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
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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> }) => {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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,
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
});
|
Loading…
Reference in New Issue