Browse Source

Reimplemented round videos

master
Eduard Kuzmenko 4 years ago
parent
commit
8fe6968c35
  1. 17
      src/components/animationIntersector.ts
  2. 4
      src/components/appMediaPlaybackController.ts
  3. 177
      src/components/wrappers.ts
  4. 59
      src/lib/mediaPlayer.ts
  5. 4
      src/scss/partials/_chatBubble.scss
  6. 49
      src/scss/partials/_ckin.scss
  7. 42
      src/scss/style.scss

17
src/components/animationIntersector.ts

@ -19,6 +19,7 @@ export class AnimationIntersector {
private onlyOnePlayableGroup: string = ''; private onlyOnePlayableGroup: string = '';
private intersectionLockedGroups: {[group: string]: true} = {}; private intersectionLockedGroups: {[group: string]: true} = {};
private videosLocked = false;
constructor() { constructor() {
this.observer = new IntersectionObserver((entries) => { this.observer = new IntersectionObserver((entries) => {
@ -52,6 +53,20 @@ export class AnimationIntersector {
} }
} }
}); });
rootScope.on('audio_play', ({doc}) => {
if(doc.type === 'round') {
this.videosLocked = true;
this.checkAnimations();
}
});
rootScope.on('audio_pause', () => {
if(this.videosLocked) {
this.videosLocked = false;
this.checkAnimations();
}
});
} }
public getAnimations(element: HTMLElement) { public getAnimations(element: HTMLElement) {
@ -132,7 +147,7 @@ export class AnimationIntersector {
return; return;
} }
if(blurred || (this.onlyOnePlayableGroup && this.onlyOnePlayableGroup !== group)) { if(blurred || (this.onlyOnePlayableGroup && this.onlyOnePlayableGroup !== group) || (animation instanceof HTMLVideoElement && this.videosLocked)) {
if(!animation.paused) { if(!animation.paused) {
//console.warn('pause animation:', animation); //console.warn('pause animation:', animation);
animation.pause(); animation.pause();

4
src/components/appMediaPlaybackController.ts

@ -177,7 +177,7 @@ class AppMediaPlaybackController {
media.safariBuffering = value; media.safariBuffering = value;
} }
onPause = (e: Event) => { onPause = (e?: Event) => {
/* const target = e.target as HTMLMediaElement; /* const target = e.target as HTMLMediaElement;
if(!isInDOM(target)) { if(!isInDOM(target)) {
this.container.append(target); this.container.append(target);
@ -188,7 +188,7 @@ class AppMediaPlaybackController {
rootScope.broadcast('audio_pause'); rootScope.broadcast('audio_pause');
}; };
onEnded = (e: Event) => { onEnded = (e?: Event) => {
this.onPause(e); this.onPause(e);
//console.log('on media end'); //console.log('on media end');

177
src/components/wrappers.ts

@ -30,6 +30,7 @@ import appImManager from '../lib/appManagers/appImManager';
import { SearchSuperContext } from './appSearchSuper.'; import { SearchSuperContext } from './appSearchSuper.';
import rootScope from '../lib/rootScope'; import rootScope from '../lib/rootScope';
import { onVideoLoad } from '../helpers/files'; import { onVideoLoad } from '../helpers/files';
import { animateSingle } from '../helpers/animation';
const MAX_VIDEO_AUTOPLAY_SIZE = 50 * 1024 * 1024; // 50 MB const MAX_VIDEO_AUTOPLAY_SIZE = 50 * 1024 * 1024; // 50 MB
@ -55,26 +56,24 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
let spanTime: HTMLElement; let spanTime: HTMLElement;
if(!noInfo) { if(!noInfo) {
if(doc.type !== 'round') { spanTime = document.createElement('span');
spanTime = document.createElement('span'); spanTime.classList.add('video-time');
spanTime.classList.add('video-time'); container.append(spanTime);
container.append(spanTime);
if(doc.type !== 'gif') { if(doc.type !== 'gif') {
spanTime.innerText = (doc.duration + '').toHHMMSS(false); spanTime.innerText = (doc.duration + '').toHHMMSS(false);
if(!noPlayButton) { if(!noPlayButton && doc.type !== 'round') {
if(canAutoplay) { if(canAutoplay) {
spanTime.classList.add('tgico', 'can-autoplay'); spanTime.classList.add('tgico', 'can-autoplay');
} else { } else {
const spanPlay = document.createElement('span'); const spanPlay = document.createElement('span');
spanPlay.classList.add('video-play', 'tgico-largeplay', 'btn-circle', 'position-center'); spanPlay.classList.add('video-play', 'tgico-largeplay', 'btn-circle', 'position-center');
container.append(spanPlay); container.append(spanPlay);
}
} }
} else {
spanTime.innerText = 'GIF';
} }
} else {
spanTime.innerText = 'GIF';
} }
} }
@ -108,80 +107,110 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
video.remove(); video.remove();
} */ } */
const video = /* doc.type === 'round' ? appMediaPlaybackController.addMedia(message.peerId, doc, message.mid) as HTMLVideoElement : */document.createElement('video'); const video = document.createElement('video');
video.classList.add('media-video'); video.classList.add('media-video');
video.muted = true;
video.setAttribute('playsinline', 'true'); video.setAttribute('playsinline', 'true');
video.muted = true;
if(doc.type === 'round') { if(doc.type === 'round') {
//video.classList.add('z-depth-1'); const globalVideo = appMediaPlaybackController.addMedia(message.peerId, doc, message.mid) as HTMLVideoElement;
const globalVideo = appMediaPlaybackController.addMedia(message.peerId, doc, message.mid);
video.classList.add('z-depth-1'); const divRound = document.createElement('div');
divRound.classList.add('media-round', 'z-depth-1');
onVideoLoad(video).then(() => { divRound.innerHTML = `<svg class="progress-ring" width="200px" height="200px">
if(globalVideo.currentTime !== globalVideo.duration) { <circle class="progress-ring__circle" stroke="white" stroke-opacity="0.3" stroke-width="3.5" cx="100" cy="100" r="93" fill="transparent" transform="rotate(-90, 100, 100)"/>
video.currentTime = globalVideo.currentTime; </svg>`;
}
if(!globalVideo.paused) { const circle = divRound.querySelector('.progress-ring__circle') as SVGCircleElement;
// с закоментированными настройками - хром выключал видео при скролле, для этого нужно было включить видео - выйти из диалога, зайти заново и проскроллить вверх const radius = circle.r.baseVal.value;
//video.autoplay = true; const circumference = 2 * Math.PI * radius;
//video.loop = false; circle.style.strokeDasharray = circumference + ' ' + circumference;
video.play(); circle.style.strokeDashoffset = '' + circumference;
}
}); spanTime.classList.add('tgico');
const canvas = document.createElement('canvas');
canvas.width = canvas.height = doc.w/* * window.devicePixelRatio */;
divRound.prepend(canvas, spanTime);
container.append(divRound);
const ctx = canvas.getContext('2d');
/* ctx.beginPath();
ctx.arc(canvas.width / 2, canvas.height / 2, canvas.width / 2, 0, Math.PI * 2);
ctx.clip(); */
const clear = () => { const clear = () => {
//console.log('clearing video'); (appImManager.chat.setPeerPromise || Promise.resolve()).finally(() => {
if(isInDOM(globalVideo)) {
return;
}
globalVideo.removeEventListener('play', onPlay);
globalVideo.removeEventListener('timeupdate', onTimeUpdate);
globalVideo.removeEventListener('pause', onPaused);
});
};
const onFrame = () => {
ctx.drawImage(globalVideo, 0, 0);
const offset = circumference - globalVideo.currentTime / globalVideo.duration * circumference;
circle.style.strokeDashoffset = '' + offset;
globalVideo.removeEventListener('timeupdate', onGlobalTimeUpdate); return !globalVideo.paused;
globalVideo.removeEventListener('play', onGlobalPlay);
globalVideo.removeEventListener('pause', onGlobalPause);
video.removeEventListener('play', onVideoPlay);
video.removeEventListener('pause', onVideoPause);
}; };
const onGlobalTimeUpdate = (e: Event) => { const onTimeUpdate = () => {
//console.log('video global timeupdate event', e, globalVideo.currentTime, globalVideo.duration); if(!globalVideo.duration) return;
if(!isInDOM(video)) {
if(!isInDOM(globalVideo)) {
clear(); clear();
return;
} }
spanTime.innerText = (globalVideo.duration - globalVideo.currentTime + '').toHHMMSS(false);
}; };
const onGlobalPlay = (e: Event) => { const onPlay = () => {
//console.log('video global play event', e); video.remove();
video.play(); divRound.classList.remove('is-paused');
animateSingle(onFrame, canvas);
}; };
const onGlobalPause = (e: Event) => { const onPaused = () => {
//console.trace('video global pause event', e, globalVideo.paused, e.eventPhase); if(!isInDOM(globalVideo)) {
video.pause(); clear();
return;
}
divRound.classList.add('is-paused');
}; };
const onVideoPlay = (e: Event) => { globalVideo.addEventListener('play', onPlay);
//console.log('video play event', e); globalVideo.addEventListener('timeupdate', onTimeUpdate);
globalVideo.addEventListener('pause', onPaused);
attachClickEvent(canvas, (e) => {
cancelEvent(e);
if(globalVideo.paused) { if(globalVideo.paused) {
globalVideo.currentTime = video.currentTime;
globalVideo.play(); globalVideo.play();
} else {
globalVideo.pause();
} }
}; });
// * this will fire when video unmounts if(globalVideo.paused) {
const onVideoPause = (e: Event) => { if(globalVideo.duration && globalVideo.currentTime !== globalVideo.duration) {
//console.trace('video pause event', e); onFrame();
if(isInDOM(video)) { onTimeUpdate();
globalVideo.pause();
globalVideo.currentTime = video.currentTime;
} else { } else {
clear(); onPaused();
} }
}; } else {
onPlay();
globalVideo.addEventListener('timeupdate', onGlobalTimeUpdate); }
globalVideo.addEventListener('play', onGlobalPlay);
globalVideo.addEventListener('pause', onGlobalPause);
video.addEventListener('play', onVideoPlay);
video.addEventListener('pause', onVideoPause);
} else { } else {
video.autoplay = true; // для safari video.autoplay = true; // для safari
} }
@ -276,7 +305,7 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
/* if(!video.paused) { /* if(!video.paused) {
video.pause(); video.pause();
} */ } */
if(doc.type !== 'round' && group) { if(group) {
animationIntersector.addAnimation(video, group); animationIntersector.addAnimation(video, group);
} }
@ -297,24 +326,16 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
deferred.resolve(); deferred.resolve();
}); });
if(doc.type !== 'round') { video.muted = true;
video.muted = true; video.loop = true;
video.loop = true; //video.play();
//video.play(); video.autoplay = true;
video.autoplay = true;
}
renderImageFromUrl(video, doc.url); renderImageFromUrl(video, doc.url);
return Promise.all([loadPromise, deferred]); 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');

59
src/lib/mediaPlayer.ts

@ -1,9 +1,8 @@
import { attachClickEvent, cancelEvent } from "../helpers/dom"; import { 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"; import { onVideoLoad } from "../helpers/files";
type SUPEREVENT = MouseEvent | TouchEvent; type SUPEREVENT = MouseEvent | TouchEvent;
@ -358,56 +357,6 @@ export default class VideoPlayer {
video.addEventListener('timeupdate', () => { video.addEventListener('timeupdate', () => {
timeElapsed.innerHTML = String(video.currentTime | 0).toHHMMSS(); timeElapsed.innerHTML = String(video.currentTime | 0).toHHMMSS();
}); });
} else if(skin === 'circle') {
const wrapper = document.createElement('div');
wrapper.classList.add('circle-time-left');
video.parentNode.insertBefore(wrapper, video);
wrapper.innerHTML = '<div class="circle-time"></div><div class="iconVolume tgico-nosound"></div>';
const circle = player.querySelector('.progress-ring__circle') as SVGCircleElement;
const radius = circle.r.baseVal.value;
const circumference = 2 * Math.PI * radius;
timeDuration = wrapper.firstElementChild as HTMLElement;
const iconVolume = wrapper.lastElementChild as HTMLElement;
circle.style.strokeDasharray = circumference + ' ' + circumference;
circle.style.strokeDashoffset = '' + circumference;
attachClickEvent(circle as any, (e) => {
cancelEvent(e);
this.togglePlay();
});
const update = () => {
const offset = circumference - video.currentTime / video.duration * circumference;
circle.style.strokeDashoffset = '' + offset;
return !video.paused;
};
const timeUpdate = () => {
const timeLeft = String((video.duration - video.currentTime) | 0).toHHMMSS();
if(timeLeft != '0') timeDuration.innerHTML = timeLeft;
};
video.addEventListener('play', () => {
iconVolume.style.display = 'none';
animateSingle(update, circle);
update();
});
video.addEventListener('pause', () => {
iconVolume.style.display = '';
});
video.addEventListener('timeupdate', () => {
timeUpdate();
});
onVideoLoad(video).then(() => {
if(!video.currentTime || video.currentTime === video.duration) return;
update();
timeUpdate();
});
} }
video.addEventListener('play', () => { video.addEventListener('play', () => {
@ -465,12 +414,6 @@ export default class VideoPlayer {
</div> </div>
</div> </div>
</div>`; </div>`;
} else if(skin === 'circle') {
return `
<svg class="progress-ring" width="200px" height="200px">
<circle class="progress-ring__circle" stroke="white" stroke-opacity="0.3" stroke-width="3.5" cx="100" cy="100" r="93" fill="transparent" transform="rotate(-90, 100, 100)"/>
</svg>
`;
} }
} }

4
src/scss/partials/_chatBubble.scss

@ -520,8 +520,9 @@ $bubble-margin: .25rem;
max-width: 200px !important; max-width: 200px !important;
max-height: 200px !important; max-height: 200px !important;
img { .media-photo, .media-video {
border-radius: 50%; border-radius: 50%;
pointer-events: none;
} }
} }
} }
@ -1239,6 +1240,7 @@ $bubble-margin: .25rem;
align-items: center; align-items: center;
cursor: pointer; cursor: pointer;
user-select: none; user-select: none;
height: 1.125rem;
&.can-autoplay:after { &.can-autoplay:after {
content: $tgico-nosound; content: $tgico-nosound;

49
src/scss/partials/_ckin.scss

@ -402,52 +402,3 @@ input[type=range] {
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
} }
video[data-ckin="circle"] {
border-radius: 50%;
overflow: hidden;
position: relative;
z-index: -1;
}
.progress-ring {
position: absolute;
top: 0;
left: 0;
cursor: pointer;
&__circle {
transition: stroke-dashoffset;
}
}
.ckin__player.circle {
position: relative;
width: 200px;
height: 200px;
.circle-time-left {
color: #fff;
position: absolute;
top: .1875rem;
left: .1875rem;
border-radius: 12px;
background-color: rgba(0, 0, 0, .23);
padding: 1px 6px 2px 7px;
height: 1.125rem;
z-index: 2;
display: flex;
align-items: center;
}
.circle-time {
font-size: .75rem;
//margin-top: 1px;
}
.iconVolume {
padding-left: .25rem;
display: flex;
align-items: center;
font-size: 1.125rem;
}
}

42
src/scss/style.scss

@ -1142,7 +1142,7 @@ middle-ellipsis-element {
fill: rgba(0, 0, 0, .08); fill: rgba(0, 0, 0, .08);
} }
.media-photo, .media-video, .media-sticker { .media-photo, .media-video, .media-sticker, .media-round {
position: absolute; position: absolute;
top: 0; top: 0;
right: 0; right: 0;
@ -1165,3 +1165,43 @@ middle-ellipsis-element {
.media-sticker { .media-sticker {
margin: auto; margin: auto;
} }
.media-round {
max-width: 200px;
max-height: 200px;
z-index: 1;
canvas {
width: 100%;
height: 100%;
border-radius: 50%;
position: relative;
}
.progress-ring {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
&__circle {
transition: stroke-dashoffset;
stroke-linecap: round;
}
}
.video-time {
padding: 1px 6px 2px 7px;
background-color: rgba(0, 0, 0, .23) !important;
}
&.is-paused .video-time {
&:after {
content: $tgico-nosound;
padding-left: .25rem;
display: flex;
align-items: center;
font-size: 1.125rem;
}
}
}

Loading…
Cancel
Save