Browse Source

Voice recorder check support

Delay for open cached chat
master
morethanwords 5 years ago
parent
commit
6356b9152c
  1. 190
      src/components/chatInput.ts
  2. 2
      src/components/lazyLoadQueue.ts
  3. 50
      src/components/misc.ts
  4. 6
      src/lib/appManagers/appDialogsManager.ts
  5. 70
      src/lib/appManagers/appImManager.ts
  6. 11
      src/lib/appManagers/appMessagesManager.ts
  7. 6
      src/lib/config.ts
  8. 6
      src/lib/mtproto/transports/websocket.ts
  9. 10
      src/pages/page.ts
  10. 70
      src/pages/pageIm.ts
  11. 2
      src/scss/components/_typography.scss
  12. 3
      src/scss/partials/_chat.scss
  13. 5
      src/scss/partials/_chatBubble.scss
  14. 4
      src/scss/style.scss

190
src/components/chatInput.ts

@ -59,14 +59,7 @@ export class ChatInput { @@ -59,14 +59,7 @@ export class ChatInput {
public editMsgID = 0;
public noWebPage = false;
private recorder = new Recorder({
//encoderBitRate: 32,
//encoderPath: "../dist/encoderWorker.min.js",
encoderSampleRate: 48000,
monitorGain: 0,
numberOfChannels: 1,
recordingGain: 1
});
private recorder: any;
private recording = false;
private recordCanceled = false;
private recordTimeEl = this.inputContainer.querySelector('.record-time') as HTMLDivElement;
@ -92,6 +85,21 @@ export class ChatInput { @@ -92,6 +85,21 @@ export class ChatInput {
this.replyElements.titleEl = this.replyElements.container.querySelector('.reply-title') as HTMLDivElement;
this.replyElements.subtitleEl = this.replyElements.container.querySelector('.reply-subtitle') as HTMLDivElement;
try {
this.recorder = new Recorder({
//encoderBitRate: 32,
//encoderPath: "../dist/encoderWorker.min.js",
encoderSampleRate: 48000,
monitorGain: 0,
numberOfChannels: 1,
recordingGain: 1
});
} catch(err) {
this.btnSend.classList.remove('tgico-microphone2');
this.btnSend.classList.add('tgico-send');
console.error('Recorder constructor error:', err);
}
this.messageInput.addEventListener('keydown', (e: KeyboardEvent) => {
if(e.key == 'Enter') {
/* if(e.ctrlKey || e.metaKey) {
@ -144,13 +152,17 @@ export class ChatInput { @@ -144,13 +152,17 @@ export class ChatInput {
if(!value.trim() && !this.serializeNodes(Array.from(this.messageInput.childNodes)).trim()) {
this.messageInput.innerHTML = '';
this.btnSend.classList.remove('tgico-send');
this.btnSend.classList.add('tgico-microphone2');
if(this.recorder) {
this.btnSend.classList.remove('tgico-send');
this.btnSend.classList.add('tgico-microphone2');
}
appMessagesManager.setTyping('sendMessageCancelAction');
} else if(!this.btnSend.classList.contains('tgico-send')) {
this.btnSend.classList.add('tgico-send');
this.btnSend.classList.remove('tgico-microphone2');
} else if(!this.btnSend.classList.contains('tgico-send') || !this.recorder) {
if(this.recorder) {
this.btnSend.classList.add('tgico-send');
this.btnSend.classList.remove('tgico-microphone2');
}
let time = Date.now();
if(time - this.lastTimeType >= 6000) {
@ -462,7 +474,7 @@ export class ChatInput { @@ -462,7 +474,7 @@ export class ChatInput {
});
this.btnSend.addEventListener('click', () => {
if(this.btnSend.classList.contains('tgico-send')) {
if(this.btnSend.classList.contains('tgico-send') || !this.recorder) {
if(this.recording) {
this.recorder.stop();
} else {
@ -527,74 +539,76 @@ export class ChatInput { @@ -527,74 +539,76 @@ export class ChatInput {
opusDecodeController.setKeepAlive(false);
});
this.recorder.onstop = () => {
this.recording = false;
this.inputContainer.classList.remove('is-recording');
this.btnSend.classList.remove('tgico-send');
this.recordRippleEl.style.transform = '';
};
this.recorder.ondataavailable = (typedArray: Uint8Array) => {
if(this.recordCanceled) return;
const duration = (Date.now() - this.recordStartTime) / 1000 | 0;
const dataBlob = new Blob([typedArray], {type: 'audio/ogg'});
/* const fileName = new Date().toISOString() + ".opus";
console.log('Recorder data received', typedArray, dataBlob); */
/* var url = URL.createObjectURL( dataBlob );
var audio = document.createElement('audio');
audio.controls = true;
audio.src = url;
var link = document.createElement('a');
link.href = url;
link.download = fileName;
link.innerHTML = link.download;
var li = document.createElement('li');
li.appendChild(link);
li.appendChild(audio);
document.body.append(li);
return; */
let perf = performance.now();
opusDecodeController.decode(typedArray, true).then(result => {
console.log('WAVEFORM!:', /* waveform, */performance.now() - perf);
opusDecodeController.setKeepAlive(false);
let peerID = appImManager.peerID;
// тут objectURL ставится уже с audio/wav
appMessagesManager.sendFile(peerID, dataBlob, {
isVoiceMessage: true,
isMedia: true,
duration,
waveform: result.waveform,
objectURL: result.url
if(this.recorder) {
this.recorder.onstop = () => {
this.recording = false;
this.inputContainer.classList.remove('is-recording');
this.btnSend.classList.remove('tgico-send');
this.recordRippleEl.style.transform = '';
};
this.recorder.ondataavailable = (typedArray: Uint8Array) => {
if(this.recordCanceled) return;
const duration = (Date.now() - this.recordStartTime) / 1000 | 0;
const dataBlob = new Blob([typedArray], {type: 'audio/ogg'});
/* const fileName = new Date().toISOString() + ".opus";
console.log('Recorder data received', typedArray, dataBlob); */
/* var url = URL.createObjectURL( dataBlob );
var audio = document.createElement('audio');
audio.controls = true;
audio.src = url;
var link = document.createElement('a');
link.href = url;
link.download = fileName;
link.innerHTML = link.download;
var li = document.createElement('li');
li.appendChild(link);
li.appendChild(audio);
document.body.append(li);
return; */
let perf = performance.now();
opusDecodeController.decode(typedArray, true).then(result => {
console.log('WAVEFORM!:', /* waveform, */performance.now() - perf);
opusDecodeController.setKeepAlive(false);
let peerID = appImManager.peerID;
// тут objectURL ставится уже с audio/wav
appMessagesManager.sendFile(peerID, dataBlob, {
isVoiceMessage: true,
isMedia: true,
duration,
waveform: result.waveform,
objectURL: result.url
});
});
});
/* const url = URL.createObjectURL(dataBlob);
var audio = document.createElement('audio');
audio.controls = true;
audio.src = url;
var link = document.createElement('a');
link.href = url;
link.download = fileName;
link.innerHTML = link.download;
var li = document.createElement('li');
li.appendChild(link);
li.appendChild(audio);
recordingslist.appendChild(li); */
};
/* const url = URL.createObjectURL(dataBlob);
var audio = document.createElement('audio');
audio.controls = true;
audio.src = url;
var link = document.createElement('a');
link.href = url;
link.download = fileName;
link.innerHTML = link.download;
var li = document.createElement('li');
li.appendChild(link);
li.appendChild(audio);
recordingslist.appendChild(li); */
};
}
let emoticonsDisplayTimeout = 0;
this.toggleEmoticons.onmouseover = (e) => {
@ -650,8 +664,11 @@ export class ChatInput { @@ -650,8 +664,11 @@ export class ChatInput {
} else {
this.editMsgID = 0;
this.messageInput.innerHTML = '';
this.btnSend.classList.remove('tgico-send');
this.btnSend.classList.add('tgico-microphone2');
if(this.recorder) {
this.btnSend.classList.remove('tgico-send');
this.btnSend.classList.add('tgico-microphone2');
}
}
}
@ -686,8 +703,11 @@ export class ChatInput { @@ -686,8 +703,11 @@ export class ChatInput {
this.replyElements.container.classList.remove('active');
this.willSendWebPage = null;
this.messageInput.innerText = '';
this.btnSend.classList.remove('tgico-send');
this.btnSend.classList.add('tgico-microphone2');
if(this.recorder) {
this.btnSend.classList.remove('tgico-send');
this.btnSend.classList.add('tgico-microphone2');
}
}
}

2
src/components/lazyLoadQueue.ts

@ -13,7 +13,7 @@ export default class LazyLoadQueue { @@ -13,7 +13,7 @@ export default class LazyLoadQueue {
private unlockResolve: () => void = null;
private log = console.log.bind(console, '[LL]:');
private debug = false;
private debug = true;
private observer: IntersectionObserver;

50
src/components/misc.ts

@ -1,9 +1,9 @@ @@ -1,9 +1,9 @@
import { whichChild, findUpTag, cancelEvent } from "../lib/utils";
import Config from "../lib/config";
import Config, { touchSupport } from "../lib/config";
let rippleClickID = 0;
export function ripple(elem: HTMLElement, callback: (id: number) => Promise<boolean | void> = () => Promise.resolve(), onEnd: (id: number) => void = null) {
return;
//return;
if(elem.querySelector('.c-ripple')) return;
let r = document.createElement('div');
@ -95,30 +95,32 @@ export function ripple(elem: HTMLElement, callback: (id: number) => Promise<bool @@ -95,30 +95,32 @@ export function ripple(elem: HTMLElement, callback: (id: number) => Promise<bool
//});
};
let touchEnd = () => {
handler && handler();
};
let touchStartFired = false;
elem.addEventListener('touchstart', (e) => {
if(e.touches.length > 1) {
return;
}
console.log('touchstart', e);
touchStartFired = true;
let {clientX, clientY} = e.touches[0];
drawRipple(clientX, clientY);
window.addEventListener('touchend', touchEnd, {once: true});
window.addEventListener('touchmove', (e) => {
e.cancelBubble = true;
e.stopPropagation();
if(touchSupport) {
let touchEnd = () => {
handler && handler();
window.removeEventListener('touchend', touchEnd);
}, {once: true});
});
};
elem.addEventListener('touchstart', (e) => {
if(e.touches.length > 1) {
return;
}
console.log('touchstart', e);
touchStartFired = true;
let {clientX, clientY} = e.touches[0];
drawRipple(clientX, clientY);
window.addEventListener('touchend', touchEnd, {once: true});
window.addEventListener('touchmove', (e) => {
e.cancelBubble = true;
e.stopPropagation();
handler && handler();
window.removeEventListener('touchend', touchEnd);
}, {once: true});
}, {passive: true});
}
elem.addEventListener('mousedown', (e) => {
if(elem.dataset.ripple == '0') {

6
src/lib/appManagers/appDialogsManager.ts

@ -291,6 +291,8 @@ export class AppDialogsManager { @@ -291,6 +291,8 @@ export class AppDialogsManager {
private contextMenu = new DialogsContextMenu([this.chatList, this.chatListArchived]);
private debug = false;
constructor() {
this.chatsPreloader = putPreloader(null, true);
@ -493,7 +495,7 @@ export class AppDialogsManager { @@ -493,7 +495,7 @@ export class AppDialogsManager {
else this.loadedAll = true;
}
this.log('getDialogs ' + loadCount + ' dialogs by offset:', offsetIndex, result, this.scroll.length, archived);
this.debug && this.log('getDialogs ' + loadCount + ' dialogs by offset:', offsetIndex, result, this.scroll.length, archived);
this.scroll.onScroll();
} catch(err) {
this.log.error(err);
@ -585,7 +587,7 @@ export class AppDialogsManager { @@ -585,7 +587,7 @@ export class AppDialogsManager {
(dialog.folder_id == 1 ? this.scrollArchived : this.scroll).reorder();
this.log('setDialogPosition:', dialog, dom, pos);
this.debug && this.log('setDialogPosition:', dialog, dom, pos);
}
public setPinnedDelimiter() {

70
src/lib/appManagers/appImManager.ts

@ -15,7 +15,6 @@ import lottieLoader from "../lottieLoader"; @@ -15,7 +15,6 @@ import lottieLoader from "../lottieLoader";
import appMediaViewer from "./appMediaViewer";
import appSidebarLeft from "./appSidebarLeft";
import appChatsManager from "./appChatsManager";
import apiUpdatesManager from './apiUpdatesManager';
import { wrapDocument, wrapPhoto, wrapVideo, wrapSticker, wrapReply, wrapAlbum, wrapPoll } from '../../components/wrappers';
import ProgressivePreloader from '../../components/preloader';
import { openBtnMenu, formatPhoneNumber, positionMenu, ripple, parseMenuButtonsTo, horizontalMenu } from '../../components/misc';
@ -31,13 +30,13 @@ import AvatarElement from '../../components/avatar'; @@ -31,13 +30,13 @@ import AvatarElement from '../../components/avatar';
import appInlineBotsManager from './AppInlineBotsManager';
import StickyIntersector from '../../components/stickyIntersector';
import { PopupPeerButton, PopupPeer } from '../../components/popup';
import { mediaSizes } from '../config';
import { mediaSizes, touchSupport } from '../config';
console.log('appImManager included!');
console.log('appImManager included33!');
appSidebarLeft; // just to include
let testScroll = false;
const testScroll = true;
const IGNOREACTIONS = ['messageActionChannelMigrateFrom'];
@ -195,14 +194,7 @@ class ChatContextMenu { @@ -195,14 +194,7 @@ class ChatContextMenu {
});
this.buttons.pin.addEventListener('click', () => {
apiManager.invokeApi('messages.updatePinnedMessage', {
flags: 0,
peer: appPeersManager.getInputPeerByID($rootScope.selectedPeerID),
id: this.msgID
}).then(updates => {
/////this.log('pinned updates:', updates);
apiUpdatesManager.processUpdateMessage(updates);
});
appMessagesManager.updatePinnedMessage($rootScope.selectedPeerID, this.msgID);
});
}
}
@ -765,6 +757,7 @@ export class AppImManager { @@ -765,6 +757,7 @@ export class AppImManager {
public setPinnedMessage(message: any) {
/////this.log('setting pinned message', message);
return;
this.pinnedMessageContainer.dataset.mid = '' + message.mid;
this.topbar.classList.add('is-pinned-shown');
this.pinnedMessageContent.innerHTML = message.rReply;
@ -826,16 +819,18 @@ export class AppImManager { @@ -826,16 +819,18 @@ export class AppImManager {
this.onScrollRAF = window.requestAnimationFrame(() => {
lottieLoader.checkAnimations(false, 'chat');
if(this.isScrollingTimeout) {
clearTimeout(this.isScrollingTimeout);
} else if(!this.chatInner.classList.contains('is-scrolling')) {
this.chatInner.classList.add('is-scrolling');
if(!touchSupport) {
if(this.isScrollingTimeout) {
clearTimeout(this.isScrollingTimeout);
} else if(!this.chatInner.classList.contains('is-scrolling')) {
this.chatInner.classList.add('is-scrolling');
}
this.isScrollingTimeout = setTimeout(() => {
this.chatInner.classList.remove('is-scrolling');
this.isScrollingTimeout = 0;
}, 300);
}
this.isScrollingTimeout = setTimeout(() => {
this.chatInner.classList.remove('is-scrolling');
this.isScrollingTimeout = 0;
}, 300);
if(this.scroll.scrollHeight - Math.round(this.scroll.scrollTop + this.scroll.offsetHeight) <= 1/* <= 5 */) {
this.scroll.parentElement.classList.add('scrolled-down');
@ -861,6 +856,27 @@ export class AppImManager { @@ -861,6 +856,27 @@ export class AppImManager {
this.scroll.addEventListener('scroll', this.onScroll.bind(this));
this.scroll.parentElement.classList.add('scrolled-down');
if(touchSupport) {
this.scroll.addEventListener('touchmove', () => {
if(this.isScrollingTimeout) {
clearTimeout(this.isScrollingTimeout);
} else if(!this.chatInner.classList.contains('is-scrolling')) {
this.chatInner.classList.add('is-scrolling');
}
this.scroll.addEventListener('touchend', () => {
if(this.isScrollingTimeout) {
clearTimeout(this.isScrollingTimeout);
}
this.isScrollingTimeout = setTimeout(() => {
this.chatInner.classList.remove('is-scrolling');
this.isScrollingTimeout = 0;
}, 300);
}, {passive: true})
}, {passive: true});
}
}
public setPeerStatus(needClear = false) {
@ -1026,10 +1042,6 @@ export class AppImManager { @@ -1026,10 +1042,6 @@ export class AppImManager {
this.log('setPeer peerID:', this.peerID, dialog, lastMsgID, topMessage);
if(mediaSizes.isMobile) {
this.selectTab(1);
}
const isJump = lastMsgID != topMessage;
// add last message, bc in getHistory will load < max_id
const additionMsgID = isJump ? 0 : topMessage;
@ -1066,6 +1078,10 @@ export class AppImManager { @@ -1066,6 +1078,10 @@ export class AppImManager {
//oldChatInner.remove();
!samePeer && this.finishPeerChange();
this.preloader.attach(this.bubblesContainer);
if(mediaSizes.isMobile) {
this.selectTab(1);
}
}
//console.timeEnd('appImManager setPeer pre promise');
@ -1083,6 +1099,10 @@ export class AppImManager { @@ -1083,6 +1099,10 @@ export class AppImManager {
if(pinned && !pinned.deleted) {
this.setPinnedMessage(pinned);
}
if(mediaSizes.isMobile) {
this.selectTab(1);
}
} else {
this.preloader.detach();
}

11
src/lib/appManagers/appMessagesManager.ts

@ -1678,6 +1678,17 @@ export class AppMessagesManager { @@ -1678,6 +1678,17 @@ export class AppMessagesManager {
public getPinnedMessage(peerID: number) {
return this.getMessage(this.pinnedMessages[peerID] || 0);
}
public updatePinnedMessage(peerID: number, msgID: number) {
apiManager.invokeApi('messages.updatePinnedMessage', {
flags: 0,
peer: appPeersManager.getInputPeerByID(peerID),
id: msgID
}).then(updates => {
/////console.log('pinned updates:', updates);
apiUpdatesManager.processUpdateMessage(updates);
});
}
public saveMessages(apiMessages: any[], options: {
isNew?: boolean,

6
src/lib/config.ts

@ -89,12 +89,16 @@ class MediaSizes { @@ -89,12 +89,16 @@ class MediaSizes {
export const mediaSizes = new MediaSizes();
// @ts-ignore
export const touchSupport = ('ontouchstart' in window) || (window.DocumentTouch && document instanceof DocumentTouch);
const Config = {
Emoji,
LatinizeMap,
TLD,
Countries,
MediaSizes: mediaSizes
MediaSizes: mediaSizes,
touchSupport
};
(window as any).Config = Config;
export default Config;

6
src/lib/mtproto/transports/websocket.ts

@ -146,9 +146,9 @@ export default class Socket extends MTTransport { @@ -146,9 +146,9 @@ export default class Socket extends MTTransport {
this.ws = new WebSocket(this.url, 'binary');
this.ws.binaryType = 'arraybuffer';
this.ws.onopen = this.handleOpen;
this.ws.onclose = this.handleClose;
this.ws.onmessage = this.handleMessage;
this.ws.addEventListener('open', this.handleOpen);
this.ws.addEventListener('close', this.handleClose);
this.ws.addEventListener('message', this.handleMessage);
};
handleOpen = () => {

10
src/pages/page.ts

@ -17,9 +17,13 @@ export default class Page { @@ -17,9 +17,13 @@ export default class Page {
if(!this.installed) {
if(this.onFirstMount) {
let res = this.onFirstMount(...args);
if(res instanceof Promise) {
await res;
try {
const res = this.onFirstMount(...args);
if(res instanceof Promise) {
await res;
}
} catch(err) {
console.error('PAGE MOUNT ERROR:', err);
}
}

70
src/pages/pageIm.ts

@ -5,7 +5,48 @@ import Page from "./page"; @@ -5,7 +5,48 @@ import Page from "./page";
let onFirstMount = () => {
//return;
let promise = import('../lib/appManagers/appImManager')/* Promise.resolve() */.then(() => {//import('../lib/services').then(services => {
const promise = import('../lib/appManagers/appImManager');
promise.finally(() => {
//alert('pageIm!');
//AudioContext && global.navigator && global.navigator.mediaDevices && global.navigator.mediaDevices.getUserMedia && global.WebAssembly;
/* // @ts-ignore
var AudioContext = globalThis.AudioContext || globalThis.webkitAudioContext;
alert('AudioContext:' + typeof(AudioContext));
// @ts-ignore
alert('global.navigator:' + typeof(navigator));
alert('navigator.mediaDevices:' + typeof(navigator.mediaDevices));
alert('navigator.mediaDevices.getUserMedia:' + typeof(navigator.mediaDevices?.getUserMedia));
alert('global.WebAssembly:' + typeof(WebAssembly)); */
// @ts-ignore
if(process.env.NODE_ENV != 'production') {
import('../lib/services');
}
//(Array.from(document.getElementsByClassName('rp')) as HTMLElement[]).forEach(el => ripple(el));
Array.from(document.getElementsByClassName('btn-menu-toggle')).forEach((el) => {
el.addEventListener('click', (e) => {
//console.log('click pageIm');
if(!el.classList.contains('btn-menu-toggle')) return false;
//window.removeEventListener('mousemove', onMouseMove);
let openedMenu = el.querySelector('.btn-menu') as HTMLDivElement;
e.cancelBubble = true;
if(el.classList.contains('menu-open')) {
el.classList.remove('menu-open');
openedMenu.classList.remove('active');
} else {
openBtnMenu(openedMenu);
}
});
});
})
//let promise = /* Promise.resolve() */.then(() => {//import('../lib/services').then(services => {
/* fetch('assets/img/camomile.jpg')
.then(res => res.blob())
.then(blob => {
@ -44,32 +85,7 @@ let onFirstMount = () => { @@ -44,32 +85,7 @@ let onFirstMount = () => {
toggleEmoticons.classList.toggle('active');
}; */
// @ts-ignore
if(process.env.NODE_ENV != 'production') {
import('../lib/services');
}
//(Array.from(document.getElementsByClassName('rp')) as HTMLElement[]).forEach(el => ripple(el));
Array.from(document.getElementsByClassName('btn-menu-toggle')).forEach((el) => {
el.addEventListener('click', (e) => {
//console.log('click pageIm');
if(!el.classList.contains('btn-menu-toggle')) return false;
//window.removeEventListener('mousemove', onMouseMove);
let openedMenu = el.querySelector('.btn-menu') as HTMLDivElement;
e.cancelBubble = true;
if(el.classList.contains('menu-open')) {
el.classList.remove('menu-open');
openedMenu.classList.remove('active');
} else {
openBtnMenu(openedMenu);
}
});
});
});
//});
return promise;
};

2
src/scss/components/_typography.scss

@ -4,9 +4,9 @@ a { @@ -4,9 +4,9 @@ a {
html {
line-height: 1.5;
font-weight: normal;
}
h1, h2, h3, h4, h5, h6 {
line-height: 1.3;
}

3
src/scss/partials/_chat.scss

@ -599,6 +599,7 @@ $time-background: rgba(0, 0, 0, .35); @@ -599,6 +599,7 @@ $time-background: rgba(0, 0, 0, .35);
overflow: hidden;
position: relative;
padding: 0 .5rem;
-webkit-mask-image: -webkit-radial-gradient(circle, white 100%, black 100%); // fix safari overflow
> .scrollable {
padding: 0 .75rem;
@ -683,7 +684,7 @@ $time-background: rgba(0, 0, 0, .35); @@ -683,7 +684,7 @@ $time-background: rgba(0, 0, 0, .35);
}
&.is-scrolling .is-sticky {
opacity: 1;
opacity: 0.99999; // 0.99999 сделано для сафари, т.к. без этого будет прыжок при скролле в самом низу или верху
}
}

5
src/scss/partials/_chatBubble.scss

@ -90,10 +90,11 @@ @@ -90,10 +90,11 @@
//z-index: 3;
z-index: 2;
pointer-events: none;
transition: opacity .3s ease;
opacity: 0.99999; // for safari
&.is-sticky {
transition: opacity .3s ease;
opacity: 0;
opacity: 0.00001; // for safari
}
}

4
src/scss/style.scss

@ -211,6 +211,10 @@ input { @@ -211,6 +211,10 @@ input {
caret-color: $button-primary-background;
}
input, textarea {
-webkit-appearance: none;
}
.subtitle {
/* font-weight: 500; */
color: #707579;

Loading…
Cancel
Save