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;
}
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 clientVideos: {[key: number]: RemoteClientVideoController} = {};
private currentSpotlight: string;
constructor(events: Registry<ChannelVideoEvents>, 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

View File

@ -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
}
}

View File

@ -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;
}

View File

@ -151,12 +151,21 @@ const VideoPlayer = React.memo((props: { videoId: string }) => {
return null;
});
const VideoContainer = React.memo((props: { videoId: string }) => (
<div className={cssStyle.videoContainer}>
<VideoPlayer videoId={props.videoId} />
<VideoInfo videoId={props.videoId} />
</div>
));
const VideoContainer = React.memo((props: { videoId: string }) => {
const events = useContext(EventContext);
return (
<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 events = useContext(EventContext);
@ -170,7 +179,7 @@ const VideoBarArrow = React.memo((props: { direction: "left" | "right", containe
</div>
</div>
);
})
});
const VideoBar = () => {
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> }) => {
return (
<EventContext.Provider value={props.events}>
@ -257,6 +291,7 @@ export const ChannelVideoRenderer = (props: { handlerId: string, events: Registr
<div className={cssStyle.panel}>
<VideoBar />
<ExpendArrow />
<Spotlight />
</div>
</HandlerIdContext.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 = 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"));