Some video chat updates
parent
ea4e08c2f6
commit
1ca64c82e2
|
@ -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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cssStyle.videoContainer}
|
||||||
|
onDoubleClick={() => events.fire("action_set_spotlight", { videoId: props.videoId })}
|
||||||
|
onContextMenu={event => {
|
||||||
|
event.preventDefault()
|
||||||
|
}}
|
||||||
|
>
|
||||||
<VideoPlayer videoId={props.videoId} />
|
<VideoPlayer videoId={props.videoId} />
|
||||||
<VideoInfo videoId={props.videoId} />
|
<VideoInfo videoId={props.videoId} />
|
||||||
</div>
|
</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>
|
||||||
|
|
|
@ -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"));
|
||||||
|
|
Loading…
Reference in New Issue