From 1ca64c82e2fa634ad6fce532416eac57fbce059f Mon Sep 17 00:00:00 2001 From: WolverinDEV Date: Thu, 12 Nov 2020 20:53:56 +0100 Subject: [PATCH] Some video chat updates --- shared/js/ConnectionHandler.ts | 3 +- shared/js/ui/frames/video/Controller.ts | 33 ++++++++++++++++ shared/js/ui/frames/video/Definitions.ts | 5 +++ shared/js/ui/frames/video/Renderer.scss | 43 +++++++++++++++------ shared/js/ui/frames/video/Renderer.tsx | 49 ++++++++++++++++++++---- web/app/rtc/Connection.ts | 15 +++++++- 6 files changed, 127 insertions(+), 21 deletions(-) diff --git a/shared/js/ConnectionHandler.ts b/shared/js/ConnectionHandler.ts index 920eee2e..c012cb1e 100644 --- a/shared/js/ConnectionHandler.ts +++ b/shared/js/ConnectionHandler.ts @@ -404,7 +404,8 @@ export class ConnectionHandler { return; } if(this.serverFeatures.supportsFeature(ServerFeature.WHISPER_ECHO)) { - spawnEchoTestModal(this); + /* FIXME: Reenable */ + //spawnEchoTestModal(this); } }); } diff --git a/shared/js/ui/frames/video/Controller.ts b/shared/js/ui/frames/video/Controller.ts index ed31212f..3859ffdb 100644 --- a/shared/js/ui/frames/video/Controller.ts +++ b/shared/js/ui/frames/video/Controller.ts @@ -204,8 +204,12 @@ class ChannelVideoController { private localVideoController: LocalVideoController; private clientVideos: {[key: number]: RemoteClientVideoController} = {}; + private currentSpotlight: string; + constructor(events: Registry, connection: ConnectionHandler) { this.events = events; + this.events.enableDebug("vc-panel"); + this.connection = connection; this.videoConnection = this.connection.serverConnection.getVideoConnection(); this.connection.events().one("notify_handler_initialized", () => { @@ -235,12 +239,17 @@ class ChannelVideoController { const events = this.eventListener = []; this.events.on("action_toggle_expended", event => { if(event.expended === this.expended) { return; } + this.expended = event.expended; + this.notifyVideoList(); this.events.fire_react("notify_expended", { expended: this.expended }); }); + this.events.on("action_set_spotlight", event => this.setSpotlight(event.videoId)); + this.events.on("query_expended", () => this.events.fire_react("notify_expended", { expended: this.expended })); this.events.on("query_videos", () => this.notifyVideoList()); + this.events.on("query_spotlight", () => this.notifySpotlight()); this.events.on("query_video_info", event => { const controller = this.findVideoById(event.videoId); @@ -323,6 +332,16 @@ class ChannelVideoController { })); } + setSpotlight(videoId: string | undefined) { + if(this.currentSpotlight === videoId) { return; } + + /* TODO: test if the video event exists? */ + + this.currentSpotlight = videoId; + this.notifySpotlight() + this.notifyVideoList(); + } + private static shouldIgnoreClient(client: ClientEntry) { return (client instanceof MusicClientEntry || client.properties.client_type_exact === ClientType.CLIENT_QUERY); } @@ -352,11 +371,13 @@ class ChannelVideoController { } private resetClientVideos() { + this.currentSpotlight = undefined; for(const clientId of Object.keys(this.clientVideos)) { this.destroyClientVideo(parseInt(clientId)); } this.notifyVideoList(); + this.notifySpotlight(); } private destroyClientVideo(clientId: number) : boolean { @@ -365,6 +386,11 @@ class ChannelVideoController { video.callbackBroadcastStateChanged = undefined; video.destroy(); delete this.clientVideos[clientId]; + + if(video.videoId === this.currentSpotlight) { + this.currentSpotlight = undefined; + this.notifySpotlight(); + } return true; } else { return false; @@ -380,6 +406,10 @@ class ChannelVideoController { this.clientVideos[client.clientId()] = controller; } + private notifySpotlight() { + this.events.fire_react("notify_spotlight", { videoId: this.currentSpotlight }); + } + private notifyVideoList() { const videoIds = []; @@ -409,6 +439,9 @@ class ChannelVideoController { } this.updateVisibility(videoCount !== 0); + if(this.expended) { + videoIds.remove(this.currentSpotlight); + } this.events.fire_react("notify_videos", { videoIds: videoIds diff --git a/shared/js/ui/frames/video/Definitions.ts b/shared/js/ui/frames/video/Definitions.ts index 8ab13e0d..fbc523a2 100644 --- a/shared/js/ui/frames/video/Definitions.ts +++ b/shared/js/ui/frames/video/Definitions.ts @@ -20,11 +20,13 @@ export type ChannelVideo ={ export interface ChannelVideoEvents { action_toggle_expended: { expended: boolean }, action_video_scroll: { direction: "left" | "right" }, + action_set_spotlight: { videoId: string | undefined }, query_expended: {}, query_videos: {}, query_video: { videoId: string }, query_video_info: { videoId: string }, + query_spotlight: {}, notify_expended: { expended: boolean }, notify_videos: { @@ -45,5 +47,8 @@ export interface ChannelVideoEvents { notify_video_arrows: { left: boolean, right: boolean + }, + notify_spotlight: { + videoId: string | undefined } } \ No newline at end of file diff --git a/shared/js/ui/frames/video/Renderer.scss b/shared/js/ui/frames/video/Renderer.scss index be82fbdf..01406bda 100644 --- a/shared/js/ui/frames/video/Renderer.scss +++ b/shared/js/ui/frames/video/Renderer.scss @@ -48,7 +48,7 @@ $small_height: 10em; right: 0; display: flex; - flex-direction: row; + flex-direction: column; justify-content: stretch; height: $small_height; @@ -96,14 +96,15 @@ $small_height: 10em; position: relative; height: $small_height; + width: 100%; + + flex-shrink: 0; + flex-grow: 0; display: flex; flex-direction: row; justify-content: flex-start; - flex-shrink: 1; - flex-grow: 1; - margin-left: .5em; margin-right: .5em; @@ -116,6 +117,14 @@ $small_height: 10em; justify-content: flex-start; overflow: hidden; + + .videoContainer { + height: ($small_height - 1em); + width: ($small_height * 16 / 9); + + flex-shrink: 0; + flex-grow: 0; + } } .arrow { @@ -172,17 +181,29 @@ $small_height: 10em; } } +.spotlight { + display: flex; + flex-direction: column; + justify-content: stretch; + + min-height: 5em; + min-width: 5em; + + margin-left: .5em; + margin-right: .5em; + + flex-shrink: 1; + flex-grow: 1; +} + .videoContainer { position: relative; margin-top: .5em; margin-bottom: .5em; - flex-shrink: 0; - flex-grow: 0; - - height: ($small_height - 1em); - width: ($small_height * 16 / 9); + flex-shrink: 1; + flex-grow: 1; background-color: #2e2e2e; box-shadow: inset 0 0 5px #00000040; @@ -200,8 +221,8 @@ $small_height: 10em; .video { opacity: 1; - max-height: 100%; - max-width: 100%; + height: 100%; + width: 100%; align-self: center; } diff --git a/shared/js/ui/frames/video/Renderer.tsx b/shared/js/ui/frames/video/Renderer.tsx index 58c5f1a5..84572d75 100644 --- a/shared/js/ui/frames/video/Renderer.tsx +++ b/shared/js/ui/frames/video/Renderer.tsx @@ -151,12 +151,21 @@ const VideoPlayer = React.memo((props: { videoId: string }) => { return null; }); -const VideoContainer = React.memo((props: { videoId: string }) => ( -
- - -
-)); +const VideoContainer = React.memo((props: { videoId: string }) => { + const events = useContext(EventContext); + return ( +
events.fire("action_set_spotlight", { videoId: props.videoId })} + onContextMenu={event => { + event.preventDefault() + }} + > + + +
+ ); +}); const VideoBarArrow = React.memo((props: { direction: "left" | "right", containerRef: React.RefObject }) => { const events = useContext(EventContext); @@ -170,7 +179,7 @@ const VideoBarArrow = React.memo((props: { direction: "left" | "right", containe ); -}) +}); const VideoBar = () => { const events = useContext(EventContext); @@ -250,6 +259,31 @@ const VideoBar = () => { ) }; +const Spotlight = () => { + const events = useContext(EventContext); + const [ videoId, setVideoId ] = useState(() => { + events.fire("query_spotlight"); + return undefined; + }); + events.reactUse("notify_spotlight", event => setVideoId(event.videoId)); + + let body; + if(videoId) { + body = ; + } else { + body = ( +
+
No spotlight selected
+
+ ); + } + return ( +
+ {body} +
+ ) +}; + export const ChannelVideoRenderer = (props: { handlerId: string, events: Registry }) => { return ( @@ -257,6 +291,7 @@ export const ChannelVideoRenderer = (props: { handlerId: string, events: Registr
+
diff --git a/web/app/rtc/Connection.ts b/web/app/rtc/Connection.ts index 64065c66..e10a8960 100644 --- a/web/app/rtc/Connection.ts +++ b/web/app/rtc/Connection.ts @@ -364,6 +364,13 @@ export class RTCConnection { }); } + for(let key in this.peer) { + if(!key.startsWith("on")) { + continue; + } + + delete this.peer[key]; + } this.peer.close(); this.peer = undefined; } @@ -448,7 +455,6 @@ export class RTCConnection { } private handleFatalError(error: string, retryThreshold: number) { - /* TODO: Reset for the server as well! */ this.reset(false); this.failedReason = error; this.updateConnectionState(RTPConnectionState.FAILED); @@ -480,6 +486,11 @@ export class RTCConnection { private enableDtx(_sender: RTCRtpSender) { } private doInitialSetup() { + if(!('RTCPeerConnection' in window)) { + this.handleFatalError(tr("WebRTC has been disabled (RTCPeerConnection is not defined)"), 0); + return; + } + this.peer = new RTCPeerConnection({ bundlePolicy: "max-bundle", rtcpMuxPolicy: "require", @@ -597,7 +608,7 @@ export class RTCConnection { } else { if(this.localCandidateCount === 0) { logError(LogCategory.WEBRTC, tr("Received local ICE candidate finish, without having any candidates.")); - this.handleFatalError(tr("Failed to gather any ICE candidates"), 5000); + this.handleFatalError(tr("Failed to gather any ICE candidates"), 0); return; } else { logTrace(LogCategory.WEBRTC, tr("Received ICE candidate finish"));