Browse Source

Video chat fixes

master
morethanwords 3 years ago
parent
commit
cc7c5e772e
  1. 285
      src/components/groupCall/index.ts
  2. 8
      src/lib/appManagers/appGroupCallsManager.ts
  3. 12
      src/scss/partials/popups/_groupCall.scss

285
src/components/groupCall/index.ts

@ -33,9 +33,9 @@ import { IS_APPLE_MOBILE } from "../../environment/userAgent";
import mediaSizes, { ScreenSize } from "../../helpers/mediaSizes"; import mediaSizes, { ScreenSize } from "../../helpers/mediaSizes";
import toggleDisability from "../../helpers/dom/toggleDisability"; import toggleDisability from "../../helpers/dom/toggleDisability";
import { ripple } from "../ripple"; import { ripple } from "../ripple";
import debounce from "../../helpers/schedulers/debounce";
import throttle from "../../helpers/schedulers/throttle"; import throttle from "../../helpers/schedulers/throttle";
import IS_SCREEN_SHARING_SUPPORTED from "../../environment/screenSharingSupport"; import IS_SCREEN_SHARING_SUPPORTED from "../../environment/screenSharingSupport";
import ListenerSetter from "../../helpers/listenerSetter";
export enum GROUP_CALL_PARTICIPANT_MUTED_STATE { export enum GROUP_CALL_PARTICIPANT_MUTED_STATE {
UNMUTED, UNMUTED,
@ -116,6 +116,34 @@ let previousState: MovableState = {
height: 640 height: 640
}; };
const className = 'group-call';
function makeButton(listenerSetter: ListenerSetter, options: {
text?: LangPackKey,
isDanger?: boolean,
noRipple?: boolean,
callback?: () => void,
listenerSetter?: ListenerSetter
}) {
const _className = className + '-button';
const div = document.createElement('div');
div.classList.add(_className, 'rp-overflow');
if(!options.noRipple) {
ripple(div);
}
if(options.isDanger) {
div.classList.add(_className + '-red');
}
if(options.callback) {
attachClickEvent(div, options.callback, {listenerSetter: options.listenerSetter});
}
return div;
}
export default class PopupGroupCall extends PopupElement { export default class PopupGroupCall extends PopupElement {
private appGroupCallsManager: AppGroupCallsManager; private appGroupCallsManager: AppGroupCallsManager;
private appPeersManager: AppPeersManager; private appPeersManager: AppPeersManager;
@ -135,6 +163,8 @@ export default class PopupGroupCall extends PopupElement {
private movable: MovableElement; private movable: MovableElement;
private buttonsContainer: HTMLDivElement; private buttonsContainer: HTMLDivElement;
private btnFullScreen2: HTMLButtonElement; private btnFullScreen2: HTMLButtonElement;
private btnVideo: HTMLDivElement;
private btnScreen: HTMLDivElement;
constructor(options: { constructor(options: {
appGroupCallsManager: AppGroupCallsManager, appGroupCallsManager: AppGroupCallsManager,
@ -150,19 +180,18 @@ export default class PopupGroupCall extends PopupElement {
safeAssign(this, options); safeAssign(this, options);
this.videosCount = 0; this.videosCount = 0;
this.container.classList.add('group-call', 'night'); this.container.classList.add(className, 'night');
const instance = this.instance = this.appGroupCallsManager.groupCall; const instance = this.instance = this.appGroupCallsManager.groupCall;
const {listenerSetter} = this; const {listenerSetter} = this;
if(!IS_APPLE_MOBILE) { if(!IS_APPLE_MOBILE) {
const btnFullScreen = this.btnFullScreen = ButtonIcon('fullscreen'); const btnFullScreen = this.btnFullScreen = ButtonIcon('fullscreen');
const btnFullScreen2 = this.btnFullScreen2 = ButtonIcon('fullscreen group-call-cfs'); const btnFullScreen2 = this.btnFullScreen2 = ButtonIcon('fullscreen ' + className + '-cfs');
const btnExitFullScreen = this.btnExitFullScreen = ButtonIcon('smallscreen'); const btnExitFullScreen = this.btnExitFullScreen = ButtonIcon('smallscreen');
attachClickEvent(btnFullScreen, () => { attachClickEvent(btnFullScreen, this.onFullScreenClick, {listenerSetter});
requestFullScreen(this.container); attachClickEvent(btnFullScreen2, this.onFullScreenClick, {listenerSetter});
}, {listenerSetter});
attachClickEvent(btnExitFullScreen, () => { attachClickEvent(btnExitFullScreen, () => {
cancelFullScreen(); cancelFullScreen();
@ -172,22 +201,22 @@ export default class PopupGroupCall extends PopupElement {
} }
const btnInvite = this.btnInvite = ButtonIcon('adduser'); const btnInvite = this.btnInvite = ButtonIcon('adduser');
const btnShowColumn = this.btnShowColumn = ButtonIcon('rightpanel group-call-only-big'); const btnShowColumn = this.btnShowColumn = ButtonIcon('rightpanel ' + className + '-only-big');
this.toggleMovable(!IS_TOUCH_SUPPORTED); this.toggleMovable(!IS_TOUCH_SUPPORTED);
attachClickEvent(btnShowColumn, this.toggleRightColumn, {listenerSetter}); attachClickEvent(btnShowColumn, this.toggleRightColumn, {listenerSetter});
const headerInfo = document.createElement('div'); const headerInfo = document.createElement('div');
headerInfo.classList.add('group-call-header-info'); headerInfo.classList.add(className + '-header-info');
this.title.classList.add('group-call-header-title'); this.title.classList.add(className + '-header-title');
const subtitle = document.createElement('div'); const subtitle = document.createElement('div');
subtitle.classList.add('group-call-header-subtitle'); subtitle.classList.add(className + '-header-subtitle');
headerInfo.append(this.title, subtitle); headerInfo.append(this.title, subtitle);
this.header.classList.add('group-call-header'); this.header.classList.add(className + '-header');
this.header.append(...[this.btnExitFullScreen, headerInfo/* , btnInvite */, this.btnFullScreen, btnShowColumn].filter(Boolean)); this.header.append(...[this.btnExitFullScreen, headerInfo/* , btnInvite */, this.btnFullScreen, btnShowColumn].filter(Boolean));
const newHeader = this.header.cloneNode(false) as HTMLElement; const newHeader = this.header.cloneNode(false) as HTMLElement;
@ -210,6 +239,8 @@ export default class PopupGroupCall extends PopupElement {
this.groupCallTitle = new GroupCallTitleElement(this.title); this.groupCallTitle = new GroupCallTitleElement(this.title);
this.groupCallDescription = new GroupCallDescriptionElement(subtitle); this.groupCallDescription = new GroupCallDescriptionElement(subtitle);
this.groupCallBodyHeaderDescription = new GroupCallDescriptionElement(newHeaderTitle); this.groupCallBodyHeaderDescription = new GroupCallDescriptionElement(newHeaderTitle);
this.constructButtons();
this.groupCallParticipantsVideo = new GroupCallParticipantsVideoElement({ this.groupCallParticipantsVideo = new GroupCallParticipantsVideoElement({
appendTo: videosScrollable.container, appendTo: videosScrollable.container,
instance, instance,
@ -228,127 +259,6 @@ export default class PopupGroupCall extends PopupElement {
...options ...options
}); });
const className = 'group-call';
{
const buttons = this.buttonsContainer = document.createElement('div');
buttons.classList.add(className + '-buttons');
this.listenerSetter.add(this.groupCallParticipantsVideo)('toggleControls', (show) => {
this.container.classList.toggle('show-controls', show);
buttons.classList.toggle('show-controls', show);
});
const b = (options: {
text?: LangPackKey,
isDanger?: boolean,
noRipple?: boolean
}) => {
const _className = className + '-button';
const div = document.createElement('div');
div.classList.add(_className, 'rp-overflow');
if(!options.noRipple) {
ripple(div);
}
if(options.isDanger) {
div.classList.add(_className + '-red');
}
return div;
};
const btnVideo = b({
text: 'VoiceChat.Video.Stream.Video'
});
btnVideo.classList.add('tgico-videocamera_filled');
attachClickEvent(btnVideo, () => {
const toggle = toggleDisability([btnVideo], true);
this.instance.toggleVideoSharing().finally(() => {
toggle();
});
}, {listenerSetter});
const btnScreen = b({
text: 'VoiceChat.Video.Stream.Screencast'
});
btnScreen.classList.add('tgico-sharescreen_filled');
btnScreen.classList.toggle('hide', !IS_SCREEN_SHARING_SUPPORTED);
attachClickEvent(btnScreen, () => {
const toggle = toggleDisability([btnScreen], true);
this.instance.toggleScreenSharing().finally(() => {
toggle();
});
}, {listenerSetter});
const btnMute = b({noRipple: true});
btnMute.classList.add('group-call-microphone-button');
const microphoneIcon = this.groupCallMicrophoneIcon = new GroupCallMicrophoneIcon();
btnMute.append(microphoneIcon.container);
const throttledMuteClick = throttle(() => {
const participant = this.instance.participant;
if(!participant.pFlags.can_self_unmute) {
if(participant.raise_hand_rating === undefined) {
this.instance.changeRaiseHand(true);
}
} else {
this.instance.toggleMuted();
}
}, 600, true);
attachClickEvent(btnMute, throttledMuteClick, {listenerSetter});
const btnMore = b({
text: 'VoiceChat.Video.Stream.More'
});
btnMore.classList.add('tgico-settings_filled', 'btn-disabled');
btnMore.classList.toggle('hide', !IS_SCREEN_SHARING_SUPPORTED);
const btnLeave = b({
text: 'VoiceChat.Leave',
isDanger: true
});
btnLeave.classList.add('tgico-close');
attachClickEvent(btnLeave, () => {
const hangUp = (discard: boolean) => {
this.instance.hangUp(discard);
};
if(this.appChatsManager.hasRights(this.instance.chatId, 'manage_call')) {
new PopupPeer('popup-end-video-chat', {
titleLangKey: 'VoiceChat.End.Title',
descriptionLangKey: 'VoiceChat.End.Text',
checkboxes: [{
text: 'VoiceChat.End.Third'
}],
buttons: [{
langKey: 'VoiceChat.End.OK',
callback: (checkboxes) => {
hangUp(!!checkboxes.size);
},
isDanger: true,
}]
}).show();
} else {
hangUp(false);
}
}, {listenerSetter});
buttons.append(btnVideo, btnScreen, btnMute, btnMore, btnLeave);
this.container.append(buttons);
}
listenerSetter.add(rootScope)('group_call_state', (instance) => { listenerSetter.add(rootScope)('group_call_state', (instance) => {
if(this.instance === instance) { if(this.instance === instance) {
this.updateInstance(); this.updateInstance();
@ -367,6 +277,8 @@ export default class PopupGroupCall extends PopupElement {
} }
}); });
listenerSetter.add(this.groupCallParticipantsVideo)('toggleControls', this.onToggleControls);
listenerSetter.add(mediaSizes)('changeScreen', (from, to) => { listenerSetter.add(mediaSizes)('changeScreen', (from, to) => {
if(to === ScreenSize.mobile || from === ScreenSize.mobile) { if(to === ScreenSize.mobile || from === ScreenSize.mobile) {
this.toggleMovable(!IS_TOUCH_SUPPORTED); this.toggleMovable(!IS_TOUCH_SUPPORTED);
@ -394,6 +306,115 @@ export default class PopupGroupCall extends PopupElement {
this.updateInstance(); this.updateInstance();
} }
private constructButtons() {
const buttons = this.buttonsContainer = document.createElement('div');
buttons.classList.add(className + '-buttons');
const _makeButton = makeButton.bind(null, this.listenerSetter);
const btnVideo = this.btnVideo = _makeButton({
text: 'VoiceChat.Video.Stream.Video',
callback: this.onVideoClick
});
btnVideo.classList.add('tgico-videocamera_filled');
const btnScreen = this.btnScreen = _makeButton({
text: 'VoiceChat.Video.Stream.Screencast',
callback: this.onScreenClick
});
btnScreen.classList.add('tgico-sharescreen_filled');
btnScreen.classList.toggle('hide', !IS_SCREEN_SHARING_SUPPORTED);
const btnMute = _makeButton({
noRipple: true,
callback: throttle(this.onMuteClick, 600, true)
});
btnMute.classList.add(className + '-microphone-button');
const microphoneIcon = this.groupCallMicrophoneIcon = new GroupCallMicrophoneIcon();
btnMute.append(microphoneIcon.container);
const btnMore = _makeButton({
text: 'VoiceChat.Video.Stream.More'
});
btnMore.classList.add('tgico-settings_filled', 'btn-disabled');
btnMore.classList.toggle('hide', !IS_SCREEN_SHARING_SUPPORTED);
const btnLeave = _makeButton({
text: 'VoiceChat.Leave',
isDanger: true,
callback: this.onLeaveClick
});
btnLeave.classList.add('tgico-close');
buttons.append(btnVideo, btnScreen, btnMute, btnMore, btnLeave);
this.container.append(buttons);
}
private onFullScreenClick = () => {
requestFullScreen(this.container);
};
private onToggleControls = (show: boolean) => {
this.container.classList.toggle('show-controls', show);
this.buttonsContainer.classList.toggle('show-controls', show);
};
private onVideoClick = () => {
const toggle = toggleDisability([this.btnVideo], true);
this.instance.toggleVideoSharing().finally(() => {
toggle();
});
};
private onScreenClick = () => {
const toggle = toggleDisability([this.btnScreen], true);
this.instance.toggleScreenSharing().finally(() => {
toggle();
});
};
private onMuteClick = () => {
const participant = this.instance.participant;
if(!participant.pFlags.can_self_unmute) {
if(participant.raise_hand_rating === undefined) {
this.instance.changeRaiseHand(true);
}
} else {
this.instance.toggleMuted();
}
};
private onLeaveClick = () => {
const hangUp = (discard: boolean) => {
this.instance.hangUp(discard);
};
if(this.appChatsManager.hasRights(this.instance.chatId, 'manage_call')) {
new PopupPeer('popup-end-video-chat', {
titleLangKey: 'VoiceChat.End.Title',
descriptionLangKey: 'VoiceChat.End.Text',
checkboxes: [{
text: 'VoiceChat.End.Third'
}],
buttons: [{
langKey: 'VoiceChat.End.OK',
callback: (checkboxes) => {
hangUp(!!checkboxes.size);
},
isDanger: true,
}]
}).show();
} else {
hangUp(false);
}
};
public getContainer() { public getContainer() {
return this.container; return this.container;
} }

8
src/lib/appManagers/appGroupCallsManager.ts

@ -14,6 +14,7 @@ import constraintSupported, { MyMediaTrackSupportedConstraints } from "../../env
import { IS_SAFARI } from "../../environment/userAgent"; import { IS_SAFARI } from "../../environment/userAgent";
import { forEachReverse, indexOfAndSplice } from "../../helpers/array"; import { forEachReverse, indexOfAndSplice } from "../../helpers/array";
import simulateEvent from "../../helpers/dom/dispatchEvent"; import simulateEvent from "../../helpers/dom/dispatchEvent";
import noop from "../../helpers/noop";
import { safeAssign, safeReplaceObject } from "../../helpers/object"; import { safeAssign, safeReplaceObject } from "../../helpers/object";
import { nextRandomUint } from "../../helpers/random"; import { nextRandomUint } from "../../helpers/random";
import throttle from "../../helpers/schedulers/throttle"; import throttle from "../../helpers/schedulers/throttle";
@ -268,7 +269,7 @@ export class GroupCallInstance {
private hadAutoPinnedSources: Set<GroupCallOutputSource>; private hadAutoPinnedSources: Set<GroupCallOutputSource>;
private dispatchPinnedThrottled: () => void; private dispatchPinnedThrottled: () => void;
private startVideoSharingPromise: Promise<void>; private startVideoSharingPromise: Promise<void>;
startScreenSharingPromise: Promise<void>; private startScreenSharingPromise: Promise<void>;
constructor(options: { constructor(options: {
id: GroupCallInstance['id'], id: GroupCallInstance['id'],
@ -303,6 +304,11 @@ export class GroupCallInstance {
this.dispatchPinnedThrottled = throttle(() => { this.dispatchPinnedThrottled = throttle(() => {
rootScope.dispatchEvent('group_call_pinned', {instance: this, source: this.pinnedSource}); rootScope.dispatchEvent('group_call_pinned', {instance: this, source: this.pinnedSource});
}, 0, false); }, 0, false);
// possible Safari fix
const audio = new Audio();
audio.play().catch(noop);
this.elements.set('audio', audio);
} }
get connectionState() { get connectionState() {

12
src/scss/partials/popups/_groupCall.scss

@ -181,10 +181,12 @@
&-icon { &-icon {
vertical-align: middle; vertical-align: middle;
margin-right: 6px; margin-right: 6px;
margin-top: 2px; // margin-top: 2px;
line-height: 1; line-height: 1;
display: inline-block; display: inline-block;
font-size: 18px; font-size: 1.125rem;
width: 1.125rem;
height: 1.125rem;
} }
&-container { &-container {
@ -228,7 +230,9 @@
.group-call-participant-status-icon { .group-call-participant-status-icon {
font-size: 1.25rem; font-size: 1.25rem;
margin-top: 0; // margin-top: 0;
width: 1.25rem;
height: 1.25rem;
} }
} }
@ -282,9 +286,7 @@
.group-call-participant-status-container { .group-call-participant-status-container {
opacity: .6; opacity: .6;
// margin-top: -1px; // if no icon // margin-top: -1px; // if no icon
margin-top: -1px; // if with icon margin-top: -1px; // if with icon
margin-bottom: -1px; // if with icon
} }
&-left { &-left {

Loading…
Cancel
Save