Browse Source

Fix round videos

master
Eduard Kuzmenko 4 years ago
parent
commit
8dc136538a
  1. 8
      src/components/appMediaPlaybackController.ts
  2. 2
      src/components/preloader.ts
  3. 65
      src/components/wrappers.ts
  4. 21
      src/helpers/animation.ts
  5. 12
      src/helpers/files.ts
  6. 2
      src/lib/cacheStorage.ts
  7. 46
      src/lib/mediaPlayer.ts
  8. 4
      src/scss/partials/_avatar.scss
  9. 7
      src/scss/partials/_chatBubble.scss

8
src/components/appMediaPlaybackController.ts

@ -4,6 +4,7 @@ import appDocsManager, {MyDocument} from "../lib/appManagers/appDocsManager";
import { CancellablePromise, deferredPromise } from "../helpers/cancellablePromise"; import { CancellablePromise, deferredPromise } from "../helpers/cancellablePromise";
import { isSafari } from "../helpers/userAgent"; import { isSafari } from "../helpers/userAgent";
import { MOUNT_CLASS_TO } from "../lib/mtproto/mtproto_config"; import { MOUNT_CLASS_TO } from "../lib/mtproto/mtproto_config";
import { isInDOM } from "../helpers/dom";
// TODO: если удалить сообщение, и при этом аудио будет играть - оно не остановится, и можно будет по нему перейти вникуда // TODO: если удалить сообщение, и при этом аудио будет играть - оно не остановится, и можно будет по нему перейти вникуда
@ -177,6 +178,13 @@ class AppMediaPlaybackController {
} }
onPause = (e: Event) => { onPause = (e: Event) => {
/* const target = e.target as HTMLMediaElement;
if(!isInDOM(target)) {
this.container.append(target);
target.play();
return;
} */
rootScope.broadcast('audio_pause'); rootScope.broadcast('audio_pause');
}; };

2
src/components/preloader.ts

@ -237,6 +237,8 @@ export default class ProgressivePreloader {
} }
public detach() { public detach() {
//return;
this.detached = true; this.detached = true;
//return; //return;

65
src/components/wrappers.ts

@ -1,3 +1,4 @@
import type Chat from './chat/chat';
import { getEmojiToneIndex } from '../emoji'; import { getEmojiToneIndex } from '../emoji';
import { readBlobAsText } from '../helpers/blob'; import { readBlobAsText } from '../helpers/blob';
import { deferredPromise } from '../helpers/cancellablePromise'; import { deferredPromise } from '../helpers/cancellablePromise';
@ -7,7 +8,6 @@ import { formatBytes } from '../helpers/number';
import { isAppleMobile, isSafari } from '../helpers/userAgent'; import { isAppleMobile, isSafari } from '../helpers/userAgent';
import { PhotoSize } from '../layer'; import { PhotoSize } from '../layer';
import appDocsManager, { MyDocument } from "../lib/appManagers/appDocsManager"; import appDocsManager, { MyDocument } from "../lib/appManagers/appDocsManager";
import { DownloadBlob } from '../lib/appManagers/appDownloadManager';
import appMessagesManager from '../lib/appManagers/appMessagesManager'; import appMessagesManager from '../lib/appManagers/appMessagesManager';
import appPhotosManager, { MyPhoto } from '../lib/appManagers/appPhotosManager'; import appPhotosManager, { MyPhoto } from '../lib/appManagers/appPhotosManager';
import LottieLoader from '../lib/lottieLoader'; import LottieLoader from '../lib/lottieLoader';
@ -27,9 +27,9 @@ import './middleEllipsis';
import { nextRandomInt } from '../helpers/random'; import { nextRandomInt } from '../helpers/random';
import RichTextProcessor from '../lib/richtextprocessor'; import RichTextProcessor from '../lib/richtextprocessor';
import appImManager from '../lib/appManagers/appImManager'; import appImManager from '../lib/appManagers/appImManager';
import Chat from './chat/chat';
import { SearchSuperContext } from './appSearchSuper.'; import { SearchSuperContext } from './appSearchSuper.';
import rootScope from '../lib/rootScope'; import rootScope from '../lib/rootScope';
import { onVideoLoad } from '../helpers/files';
const MAX_VIDEO_AUTOPLAY_SIZE = 50 * 1024 * 1024; // 50 MB const MAX_VIDEO_AUTOPLAY_SIZE = 50 * 1024 * 1024; // 50 MB
@ -108,28 +108,28 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
video.remove(); video.remove();
} */ } */
const video = document.createElement('video'); const video = /* doc.type === 'round' ? appMediaPlaybackController.addMedia(message.peerId, doc, message.mid) as HTMLVideoElement : */document.createElement('video');
video.classList.add('media-video'); video.classList.add('media-video');
video.muted = true; video.muted = true;
video.setAttribute('playsinline', 'true'); video.setAttribute('playsinline', 'true');
if(doc.type === 'round') { if(doc.type === 'round') {
//video.muted = true; //video.classList.add('z-depth-1');
const globalVideo = appMediaPlaybackController.addMedia(message.peerId, doc, message.mid); const globalVideo = appMediaPlaybackController.addMedia(message.peerId, doc, message.mid);
video.classList.add('z-depth-1'); video.classList.add('z-depth-1');
video.addEventListener('canplay', () => { onVideoLoad(video).then(() => {
if(globalVideo.currentTime > 0) { if(globalVideo.currentTime !== globalVideo.duration) {
video.currentTime = globalVideo.currentTime; video.currentTime = globalVideo.currentTime;
} }
if(!globalVideo.paused) { if(!globalVideo.paused) {
// с закоментированными настройками - хром выключал видео при скролле, для этого нужно было включить видео - выйти из диалога, зайти заново и проскроллить вверх // с закоментированными настройками - хром выключал видео при скролле, для этого нужно было включить видео - выйти из диалога, зайти заново и проскроллить вверх
/* video.autoplay = true; //video.autoplay = true;
video.loop = false; */ //video.loop = false;
video.play(); video.play();
} }
}, {once: true}); });
const clear = () => { const clear = () => {
//console.log('clearing video'); //console.log('clearing video');
@ -160,14 +160,18 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
const onVideoPlay = (e: Event) => { const onVideoPlay = (e: Event) => {
//console.log('video play event', e); //console.log('video play event', e);
globalVideo.currentTime = video.currentTime; if(globalVideo.paused) {
globalVideo.play(); globalVideo.currentTime = video.currentTime;
globalVideo.play();
}
}; };
// * this will fire when video unmounts
const onVideoPause = (e: Event) => { const onVideoPause = (e: Event) => {
//console.trace('video pause event', e); //console.trace('video pause event', e);
if(isInDOM(video)) { if(isInDOM(video)) {
globalVideo.pause(); globalVideo.pause();
globalVideo.currentTime = video.currentTime;
} else { } else {
clear(); clear();
} }
@ -229,22 +233,25 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
return; return;
} }
let loadPromise: Promise<any> = Promise.resolve();
let preloader: ProgressivePreloader; let preloader: ProgressivePreloader;
if(message?.media?.preloader) { // means upload if(message?.media?.preloader) { // means upload
preloader = message.media.preloader as ProgressivePreloader; preloader = message.media.preloader as ProgressivePreloader;
preloader.attach(container, false); preloader.attach(container, false);
} else if(!doc.downloaded && !doc.supportsStreaming) { } else if(!doc.downloaded && !doc.supportsStreaming) {
const promise = appDocsManager.downloadDoc(doc, lazyLoadQueue?.queueId); const promise = loadPromise = appDocsManager.downloadDoc(doc, lazyLoadQueue?.queueId);
preloader = new ProgressivePreloader({ preloader = new ProgressivePreloader({
attachMethod: 'prepend' attachMethod: 'prepend'
}); });
preloader.attach(container, true, promise); preloader.attach(container, true, promise);
await promise; //if(doc.type !== 'round') {
await promise;
if(middleware && !middleware()) { if(middleware && !middleware()) {
return; return;
} }
//}
} else if(doc.supportsStreaming) { } else if(doc.supportsStreaming) {
preloader = new ProgressivePreloader({ preloader = new ProgressivePreloader({
cancelable: false, cancelable: false,
@ -256,12 +263,16 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
}, {once: true}); }, {once: true});
} }
/* if(doc.type === 'round') {
return;
} */
//console.log('loaded doc:', doc, doc.url, container); //console.log('loaded doc:', doc, doc.url, container);
const deferred = deferredPromise<void>(); const deferred = deferredPromise<void>();
//if(doc.type == 'gif'/* || true */) { //if(doc.type == 'gif'/* || true */) {
video.addEventListener(isAppleMobile ? 'loadeddata' : 'canplay', () => { onVideoLoad(video).then(() => {
/* if(!video.paused) { /* if(!video.paused) {
video.pause(); video.pause();
} */ } */
@ -273,7 +284,7 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
//setTimeout(() => { //setTimeout(() => {
deferred.resolve(); deferred.resolve();
//}, 5000); //}, 5000);
}, {once: true}); });
//} //}
if(doc.type === 'video') { if(doc.type === 'video') {
@ -286,22 +297,24 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
deferred.resolve(); deferred.resolve();
}); });
renderImageFromUrl(video, doc.url); if(doc.type !== 'round') {
if(doc.type === 'round') {
video.dataset.ckin = 'circle';
video.dataset.overlay = '1';
new VideoPlayer(video);
} else {
video.muted = true; video.muted = true;
video.loop = true; video.loop = true;
//video.play(); //video.play();
video.autoplay = true; video.autoplay = true;
} }
return deferred; renderImageFromUrl(video, doc.url);
return Promise.all([loadPromise, deferred]);
}; };
if(doc.type === 'round') {
video.dataset.ckin = 'circle';
video.dataset.overlay = '1';
new VideoPlayer(video, undefined, undefined, doc.duration);
}
/* if(doc.size >= 20e6 && !doc.downloaded) { /* if(doc.size >= 20e6 && !doc.downloaded) {
let downloadDiv = document.createElement('div'); let downloadDiv = document.createElement('div');
downloadDiv.classList.add('download'); downloadDiv.classList.add('download');

21
src/helpers/animation.ts

@ -11,6 +11,22 @@ interface AnimationInstance {
type AnimationInstanceKey = any; type AnimationInstanceKey = any;
const instances: Map<AnimationInstanceKey, AnimationInstance> = new Map(); const instances: Map<AnimationInstanceKey, AnimationInstance> = new Map();
export function createAnimationInstance(key: AnimationInstanceKey) {
cancelAnimationByKey(key);
const instance: AnimationInstance = {
isCancelled: false,
deferred: deferredPromise<void>()
};
instances.set(key, instance);
instance.deferred.then(() => {
instances.delete(key);
});
return instance;
}
export function getAnimationInstance(key: AnimationInstanceKey) { export function getAnimationInstance(key: AnimationInstanceKey) {
return instances.get(key); return instances.get(key);
} }
@ -20,15 +36,12 @@ export function cancelAnimationByKey(key: AnimationInstanceKey) {
if(instance) { if(instance) {
instance.isCancelled = true; instance.isCancelled = true;
instance.deferred.resolve(); instance.deferred.resolve();
instances.delete(key);
} }
} }
export function animateSingle(tick: Function, key: AnimationInstanceKey, instance?: AnimationInstance) { export function animateSingle(tick: Function, key: AnimationInstanceKey, instance?: AnimationInstance) {
if(!instance) { if(!instance) {
cancelAnimationByKey(key); instance = createAnimationInstance(key);
instance = { isCancelled: false, deferred: deferredPromise<void>() };
instances.set(key, instance);
} }
fastRaf(() => { fastRaf(() => {

12
src/helpers/files.ts

@ -1,4 +1,5 @@
import { pause } from "./schedulers"; import { pause } from "./schedulers";
import { isAppleMobile } from "./userAgent";
export function preloadVideo(url: string): Promise<HTMLVideoElement> { export function preloadVideo(url: string): Promise<HTMLVideoElement> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -36,3 +37,14 @@ export async function createPosterForVideo(url: string): Promise<Blob | undefine
createPosterFromVideo(video), createPosterFromVideo(video),
]); ]);
} }
export function onVideoLoad(video: HTMLVideoElement) {
return new Promise<void>((resolve) => {
if(video.readyState >= video.HAVE_METADATA) {
resolve();
return;
}
video.addEventListener(isAppleMobile ? 'loadeddata' : 'canplay', () => resolve(), {once: true});
});
}

2
src/lib/cacheStorage.ts

@ -82,7 +82,7 @@ export default class CacheStorageController {
reject(); reject();
//console.warn('CACHESTORAGE TIMEOUT'); //console.warn('CACHESTORAGE TIMEOUT');
rejected = true; rejected = true;
}, 5e3); }, 15e3);
try { try {
const cache = await this.openDatabase(); const cache = await this.openDatabase();

46
src/lib/mediaPlayer.ts

@ -1,8 +1,10 @@
import { cancelEvent } from "../helpers/dom"; import { attachClickEvent, cancelEvent } from "../helpers/dom";
import appMediaPlaybackController from "../components/appMediaPlaybackController"; import appMediaPlaybackController from "../components/appMediaPlaybackController";
import { isAppleMobile } from "../helpers/userAgent"; import { isAppleMobile } from "../helpers/userAgent";
import { isTouchSupported } from "../helpers/touchSupport"; import { isTouchSupported } from "../helpers/touchSupport";
import RangeSelector from "../components/rangeSelector"; import RangeSelector from "../components/rangeSelector";
import { animateSingle } from "../helpers/animation";
import { onVideoLoad } from "../helpers/files";
type SUPEREVENT = MouseEvent | TouchEvent; type SUPEREVENT = MouseEvent | TouchEvent;
@ -167,7 +169,7 @@ export default class VideoPlayer {
/* private videoParent: HTMLElement; /* private videoParent: HTMLElement;
private videoWhichChild: number; */ private videoWhichChild: number; */
constructor(public video: HTMLVideoElement, play = false, streamable = false) { constructor(public video: HTMLVideoElement, play = false, streamable = false, duration?: number) {
this.wrapper = document.createElement('div'); this.wrapper = document.createElement('div');
this.wrapper.classList.add('ckin__player'); this.wrapper.classList.add('ckin__player');
@ -176,7 +178,7 @@ export default class VideoPlayer {
this.skin = video.dataset.ckin ?? 'default'; this.skin = video.dataset.ckin ?? 'default';
this.stylePlayer(); this.stylePlayer(duration);
if(this.skin === 'default') { if(this.skin === 'default') {
let controls = this.wrapper.querySelector('.default__controls.ckin__controls') as HTMLDivElement; let controls = this.wrapper.querySelector('.default__controls.ckin__controls') as HTMLDivElement;
@ -199,7 +201,7 @@ export default class VideoPlayer {
} }
} }
private stylePlayer() { private stylePlayer(initDuration: number) {
const {wrapper: player, video, skin} = this; const {wrapper: player, video, skin} = this;
player.classList.add(skin); player.classList.add(skin);
@ -365,11 +367,12 @@ export default class VideoPlayer {
const circle = player.querySelector('.progress-ring__circle') as SVGCircleElement; const circle = player.querySelector('.progress-ring__circle') as SVGCircleElement;
const radius = circle.r.baseVal.value; const radius = circle.r.baseVal.value;
const circumference = 2 * Math.PI * radius; const circumference = 2 * Math.PI * radius;
timeDuration = player.querySelector('.circle-time') as HTMLElement; timeDuration = wrapper.firstElementChild as HTMLElement;
const iconVolume = player.querySelector('.iconVolume') as HTMLDivElement; const iconVolume = wrapper.lastElementChild as HTMLElement;
circle.style.strokeDasharray = circumference + ' ' + circumference; circle.style.strokeDasharray = circumference + ' ' + circumference;
circle.style.strokeDashoffset = '' + circumference; circle.style.strokeDashoffset = '' + circumference;
circle.addEventListener('click', () => { attachClickEvent(circle as any, (e) => {
cancelEvent(e);
this.togglePlay(); this.togglePlay();
}); });
@ -377,24 +380,33 @@ export default class VideoPlayer {
const offset = circumference - video.currentTime / video.duration * circumference; const offset = circumference - video.currentTime / video.duration * circumference;
circle.style.strokeDashoffset = '' + offset; circle.style.strokeDashoffset = '' + offset;
if(video.paused) { return !video.paused;
clearInterval(updateInterval); };
}
const timeUpdate = () => {
const timeLeft = String((video.duration - video.currentTime) | 0).toHHMMSS();
if(timeLeft != '0') timeDuration.innerHTML = timeLeft;
}; };
video.addEventListener('play', () => { video.addEventListener('play', () => {
iconVolume.style.display = 'none'; iconVolume.style.display = 'none';
updateInterval = window.setInterval(update, 20);
animateSingle(update, circle);
update();
}); });
video.addEventListener('pause', () => { video.addEventListener('pause', () => {
iconVolume.style.display = ''; iconVolume.style.display = '';
}); });
let updateInterval = 0;
video.addEventListener('timeupdate', () => { video.addEventListener('timeupdate', () => {
const timeLeft = String((video.duration - video.currentTime) | 0).toHHMMSS(); timeUpdate();
if(timeLeft != '0') timeDuration.innerHTML = timeLeft; });
onVideoLoad(video).then(() => {
if(!video.currentTime || video.currentTime === video.duration) return;
update();
timeUpdate();
}); });
} }
@ -406,10 +418,10 @@ export default class VideoPlayer {
this.wrapper.classList.remove('is-playing'); this.wrapper.classList.remove('is-playing');
}); });
if(video.duration > 0) { if(video.duration || initDuration) {
timeDuration.innerHTML = String(Math.round(video.duration)).toHHMMSS(); timeDuration.innerHTML = String(Math.round(video.duration || initDuration)).toHHMMSS();
} else { } else {
video.addEventListener('loadeddata', () => { onVideoLoad(video).then(() => {
timeDuration.innerHTML = String(Math.round(video.duration)).toHHMMSS(); timeDuration.innerHTML = String(Math.round(video.duration)).toHHMMSS();
}); });
} }

4
src/scss/partials/_avatar.scss

@ -78,8 +78,8 @@ avatar-element {
} }
&.emoji { &.emoji {
width: calc(1.125rem / var(--multiplier)); width: calc(1.125rem / var(--multiplier)) !important;
height: calc(1.125rem / var(--multiplier)); height: calc(1.125rem / var(--multiplier)) !important;
vertical-align: middle !important; vertical-align: middle !important;
} }
} }

7
src/scss/partials/_chatBubble.scss

@ -667,13 +667,6 @@ $bubble-margin: .25rem;
} }
} }
&.round {
.attachment {
max-height: 200px;
max-width: 200px;
}
}
.web { .web {
padding-top: 1px; padding-top: 1px;
margin: 4px 0 -5px 1px; margin: 4px 0 -5px 1px;

Loading…
Cancel
Save