Some video chat updates

canary
WolverinDEV 2020-11-12 20:53:56 +01:00
parent ea4e08c2f6
commit 1ca64c82e2
6 changed files with 127 additions and 21 deletions

View File

@ -404,7 +404,8 @@ export class ConnectionHandler {
return; return;
} }
if(this.serverFeatures.supportsFeature(ServerFeature.WHISPER_ECHO)) { if(this.serverFeatures.supportsFeature(ServerFeature.WHISPER_ECHO)) {
spawnEchoTestModal(this); /* FIXME: Reenable */
//spawnEchoTestModal(this);
} }
}); });
} }

View File

@ -204,8 +204,12 @@ class ChannelVideoController {
private localVideoController: LocalVideoController; private localVideoController: LocalVideoController;
private clientVideos: {[key: number]: RemoteClientVideoController} = {}; private clientVideos: {[key: number]: RemoteClientVideoController} = {};
private currentSpotlight: string;
constructor(events: Registry<ChannelVideoEvents>, connection: ConnectionHandler) { constructor(events: Registry<ChannelVideoEvents>, connection: ConnectionHandler) {
this.events = events; this.events = events;
this.events.enableDebug("vc-panel");
this.connection = connection; this.connection = connection;
this.videoConnection = this.connection.serverConnection.getVideoConnection(); this.videoConnection = this.connection.serverConnection.getVideoConnection();
this.connection.events().one("notify_handler_initialized", () => { this.connection.events().one("notify_handler_initialized", () => {
@ -235,12 +239,17 @@ class ChannelVideoController {
const events = this.eventListener = []; const events = this.eventListener = [];
this.events.on("action_toggle_expended", event => { this.events.on("action_toggle_expended", event => {
if(event.expended === this.expended) { return; } if(event.expended === this.expended) { return; }
this.expended = event.expended; this.expended = event.expended;
this.notifyVideoList();
this.events.fire_react("notify_expended", { expended: this.expended }); 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_expended", () => this.events.fire_react("notify_expended", { expended: this.expended }));
this.events.on("query_videos", () => this.notifyVideoList()); this.events.on("query_videos", () => this.notifyVideoList());
this.events.on("query_spotlight", () => this.notifySpotlight());
this.events.on("query_video_info", event => { this.events.on("query_video_info", event => {
const controller = this.findVideoById(event.videoId); 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) { private static shouldIgnoreClient(client: ClientEntry) {
return (client instanceof MusicClientEntry || client.properties.client_type_exact === ClientType.CLIENT_QUERY); return (client instanceof MusicClientEntry || client.properties.client_type_exact === ClientType.CLIENT_QUERY);
} }
@ -352,11 +371,13 @@ class ChannelVideoController {
} }
private resetClientVideos() { private resetClientVideos() {
this.currentSpotlight = undefined;
for(const clientId of Object.keys(this.clientVideos)) { for(const clientId of Object.keys(this.clientVideos)) {
this.destroyClientVideo(parseInt(clientId)); this.destroyClientVideo(parseInt(clientId));
} }
this.notifyVideoList(); this.notifyVideoList();
this.notifySpotlight();
} }
private destroyClientVideo(clientId: number) : boolean { private destroyClientVideo(clientId: number) : boolean {
@ -365,6 +386,11 @@ class ChannelVideoController {
video.callbackBroadcastStateChanged = undefined; video.callbackBroadcastStateChanged = undefined;
video.destroy(); video.destroy();
delete this.clientVideos[clientId]; delete this.clientVideos[clientId];
if(video.videoId === this.currentSpotlight) {
this.currentSpotlight = undefined;
this.notifySpotlight();
}
return true; return true;
} else { } else {
return false; return false;
@ -380,6 +406,10 @@ class ChannelVideoController {
this.clientVideos[client.clientId()] = controller; this.clientVideos[client.clientId()] = controller;
} }
private notifySpotlight() {
this.events.fire_react("notify_spotlight", { videoId: this.currentSpotlight });
}
private notifyVideoList() { private notifyVideoList() {
const videoIds = []; const videoIds = [];
@ -409,6 +439,9 @@ class ChannelVideoController {
} }
this.updateVisibility(videoCount !== 0); this.updateVisibility(videoCount !== 0);
if(this.expended) {
videoIds.remove(this.currentSpotlight);
}
this.events.fire_react("notify_videos", { this.events.fire_react("notify_videos", {
videoIds: videoIds videoIds: videoIds

View File

@ -20,11 +20,13 @@ export type ChannelVideo ={
export interface ChannelVideoEvents { export interface ChannelVideoEvents {
action_toggle_expended: { expended: boolean }, action_toggle_expended: { expended: boolean },
action_video_scroll: { direction: "left" | "right" }, action_video_scroll: { direction: "left" | "right" },
action_set_spotlight: { videoId: string | undefined },
query_expended: {}, query_expended: {},
query_videos: {}, query_videos: {},
query_video: { videoId: string }, query_video: { videoId: string },
query_video_info: { videoId: string }, query_video_info: { videoId: string },
query_spotlight: {},
notify_expended: { expended: boolean }, notify_expended: { expended: boolean },
notify_videos: { notify_videos: {
@ -45,5 +47,8 @@ export interface ChannelVideoEvents {
notify_video_arrows: { notify_video_arrows: {
left: boolean, left: boolean,
right: boolean right: boolean
},
notify_spotlight: {
videoId: string | undefined
} }
} }

View File

@ -48,7 +48,7 @@ $small_height: 10em;
right: 0; right: 0;
display: flex; display: flex;
flex-direction: row; flex-direction: column;
justify-content: stretch; justify-content: stretch;
height: $small_height; height: $small_height;
@ -96,14 +96,15 @@ $small_height: 10em;
position: relative; position: relative;
height: $small_height; height: $small_height;
width: 100%;
flex-shrink: 0;
flex-grow: 0;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: flex-start; justify-content: flex-start;
flex-shrink: 1;
flex-grow: 1;
margin-left: .5em; margin-left: .5em;
margin-right: .5em; margin-right: .5em;
@ -116,6 +117,14 @@ $small_height: 10em;
justify-content: flex-start; justify-content: flex-start;
overflow: hidden; overflow: hidden;
.videoContainer {
height: ($small_height - 1em);
width: ($small_height * 16 / 9);
flex-shrink: 0;
flex-grow: 0;
}
} }
.arrow { .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 { .videoContainer {
position: relative; position: relative;
margin-top: .5em; margin-top: .5em;
margin-bottom: .5em; margin-bottom: .5em;
flex-shrink: 0; flex-shrink: 1;
flex-grow: 0; flex-grow: 1;
height: ($small_height - 1em);
width: ($small_height * 16 / 9);
background-color: #2e2e2e; background-color: #2e2e2e;
box-shadow: inset 0 0 5px #00000040; box-shadow: inset 0 0 5px #00000040;
@ -200,8 +221,8 @@ $small_height: 10em;
.video { .video {
opacity: 1; opacity: 1;
max-height: 100%; height: 100%;
max-width: 100%; width: 100%;
align-self: center; align-self: center;
} }

View File

@ -151,12 +151,21 @@ const VideoPlayer = React.memo((props: { videoId: string }) => {
return null; return null;
}); });
const VideoContainer = React.memo((props: { videoId: string }) => ( const VideoContainer = React.memo((props: { videoId: string }) => {
<div className={cssStyle.videoContainer}> const events = useContext(EventContext);
<VideoPlayer videoId={props.videoId} /> return (
<VideoInfo videoId={props.videoId} /> <div
</div> className={cssStyle.videoContainer}
)); onDoubleClick={() => events.fire("action_set_spotlight", { videoId: props.videoId })}
onContextMenu={event => {
event.preventDefault()
}}
>
<VideoPlayer videoId={props.videoId} />
<VideoInfo videoId={props.videoId} />
</div>
);
});
const VideoBarArrow = React.memo((props: { direction: "left" | "right", containerRef: React.RefObject<HTMLDivElement> }) => { const VideoBarArrow = React.memo((props: { direction: "left" | "right", containerRef: React.RefObject<HTMLDivElement> }) => {
const events = useContext(EventContext); const events = useContext(EventContext);
@ -170,7 +179,7 @@ const VideoBarArrow = React.memo((props: { direction: "left" | "right", containe
</div> </div>
</div> </div>
); );
}) });
const VideoBar = () => { const VideoBar = () => {
const events = useContext(EventContext); const events = useContext(EventContext);
@ -250,6 +259,31 @@ const VideoBar = () => {
) )
}; };
const Spotlight = () => {
const events = useContext(EventContext);
const [ videoId, setVideoId ] = useState<string>(() => {
events.fire("query_spotlight");
return undefined;
});
events.reactUse("notify_spotlight", event => setVideoId(event.videoId));
let body;
if(videoId) {
body = <VideoContainer videoId={videoId} key={"video-" + videoId} />;
} else {
body = (
<div className={cssStyle.videoContainer} key={"no-video"}>
<div className={cssStyle.text}><Translatable>No spotlight selected</Translatable></div>
</div>
);
}
return (
<div className={cssStyle.spotlight}>
{body}
</div>
)
};
export const ChannelVideoRenderer = (props: { handlerId: string, events: Registry<ChannelVideoEvents> }) => { export const ChannelVideoRenderer = (props: { handlerId: string, events: Registry<ChannelVideoEvents> }) => {
return ( return (
<EventContext.Provider value={props.events}> <EventContext.Provider value={props.events}>
@ -257,6 +291,7 @@ export const ChannelVideoRenderer = (props: { handlerId: string, events: Registr
<div className={cssStyle.panel}> <div className={cssStyle.panel}>
<VideoBar /> <VideoBar />
<ExpendArrow /> <ExpendArrow />
<Spotlight />
</div> </div>
</HandlerIdContext.Provider> </HandlerIdContext.Provider>
</EventContext.Provider> </EventContext.Provider>

View File

@ -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.close();
this.peer = undefined; this.peer = undefined;
} }
@ -448,7 +455,6 @@ export class RTCConnection {
} }
private handleFatalError(error: string, retryThreshold: number) { private handleFatalError(error: string, retryThreshold: number) {
/* TODO: Reset for the server as well! */
this.reset(false); this.reset(false);
this.failedReason = error; this.failedReason = error;
this.updateConnectionState(RTPConnectionState.FAILED); this.updateConnectionState(RTPConnectionState.FAILED);
@ -480,6 +486,11 @@ export class RTCConnection {
private enableDtx(_sender: RTCRtpSender) { } private enableDtx(_sender: RTCRtpSender) { }
private doInitialSetup() { private doInitialSetup() {
if(!('RTCPeerConnection' in window)) {
this.handleFatalError(tr("WebRTC has been disabled (RTCPeerConnection is not defined)"), 0);
return;
}
this.peer = new RTCPeerConnection({ this.peer = new RTCPeerConnection({
bundlePolicy: "max-bundle", bundlePolicy: "max-bundle",
rtcpMuxPolicy: "require", rtcpMuxPolicy: "require",
@ -597,7 +608,7 @@ export class RTCConnection {
} else { } else {
if(this.localCandidateCount === 0) { if(this.localCandidateCount === 0) {
logError(LogCategory.WEBRTC, tr("Received local ICE candidate finish, without having any candidates.")); 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; return;
} else { } else {
logTrace(LogCategory.WEBRTC, tr("Received ICE candidate finish")); logTrace(LogCategory.WEBRTC, tr("Received ICE candidate finish"));