Some video chat updates
parent
ea4e08c2f6
commit
1ca64c82e2
|
@ -404,7 +404,8 @@ export class ConnectionHandler {
|
|||
return;
|
||||
}
|
||||
if(this.serverFeatures.supportsFeature(ServerFeature.WHISPER_ECHO)) {
|
||||
spawnEchoTestModal(this);
|
||||
/* FIXME: Reenable */
|
||||
//spawnEchoTestModal(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"));
|
||||
|
|
Loading…
Reference in New Issue