diff --git a/src/components/animationIntersector.ts b/src/components/animationIntersector.ts index f2c8ad58..2ecf24f9 100644 --- a/src/components/animationIntersector.ts +++ b/src/components/animationIntersector.ts @@ -19,6 +19,7 @@ export class AnimationIntersector { private onlyOnePlayableGroup: string = ''; private intersectionLockedGroups: {[group: string]: true} = {}; + private videosLocked = false; constructor() { 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) { @@ -132,7 +147,7 @@ export class AnimationIntersector { return; } - if(blurred || (this.onlyOnePlayableGroup && this.onlyOnePlayableGroup !== group)) { + if(blurred || (this.onlyOnePlayableGroup && this.onlyOnePlayableGroup !== group) || (animation instanceof HTMLVideoElement && this.videosLocked)) { if(!animation.paused) { //console.warn('pause animation:', animation); animation.pause(); diff --git a/src/components/appMediaPlaybackController.ts b/src/components/appMediaPlaybackController.ts index 553457e4..8996281d 100644 --- a/src/components/appMediaPlaybackController.ts +++ b/src/components/appMediaPlaybackController.ts @@ -177,7 +177,7 @@ class AppMediaPlaybackController { media.safariBuffering = value; } - onPause = (e: Event) => { + onPause = (e?: Event) => { /* const target = e.target as HTMLMediaElement; if(!isInDOM(target)) { this.container.append(target); @@ -188,7 +188,7 @@ class AppMediaPlaybackController { rootScope.broadcast('audio_pause'); }; - onEnded = (e: Event) => { + onEnded = (e?: Event) => { this.onPause(e); //console.log('on media end'); diff --git a/src/components/wrappers.ts b/src/components/wrappers.ts index 070c09ba..d21dd448 100644 --- a/src/components/wrappers.ts +++ b/src/components/wrappers.ts @@ -30,6 +30,7 @@ import appImManager from '../lib/appManagers/appImManager'; import { SearchSuperContext } from './appSearchSuper.'; import rootScope from '../lib/rootScope'; import { onVideoLoad } from '../helpers/files'; +import { animateSingle } from '../helpers/animation'; 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; if(!noInfo) { - if(doc.type !== 'round') { - spanTime = document.createElement('span'); - spanTime.classList.add('video-time'); - container.append(spanTime); + spanTime = document.createElement('span'); + spanTime.classList.add('video-time'); + container.append(spanTime); - if(doc.type !== 'gif') { - spanTime.innerText = (doc.duration + '').toHHMMSS(false); + if(doc.type !== 'gif') { + spanTime.innerText = (doc.duration + '').toHHMMSS(false); - if(!noPlayButton) { - if(canAutoplay) { - spanTime.classList.add('tgico', 'can-autoplay'); - } else { - const spanPlay = document.createElement('span'); - spanPlay.classList.add('video-play', 'tgico-largeplay', 'btn-circle', 'position-center'); - container.append(spanPlay); - } + if(!noPlayButton && doc.type !== 'round') { + if(canAutoplay) { + spanTime.classList.add('tgico', 'can-autoplay'); + } else { + const spanPlay = document.createElement('span'); + spanPlay.classList.add('video-play', 'tgico-largeplay', 'btn-circle', 'position-center'); + 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(); } */ - 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.muted = true; video.setAttribute('playsinline', 'true'); + video.muted = true; if(doc.type === 'round') { - //video.classList.add('z-depth-1'); - const globalVideo = appMediaPlaybackController.addMedia(message.peerId, doc, message.mid); + const globalVideo = appMediaPlaybackController.addMedia(message.peerId, doc, message.mid) as HTMLVideoElement; + + const divRound = document.createElement('div'); + divRound.classList.add('media-round', 'z-depth-1'); + + divRound.innerHTML = ` + + `; + + const circle = divRound.querySelector('.progress-ring__circle') as SVGCircleElement; + const radius = circle.r.baseVal.value; + const circumference = 2 * Math.PI * radius; + circle.style.strokeDasharray = circumference + ' ' + circumference; + circle.style.strokeDashoffset = '' + circumference; + + spanTime.classList.add('tgico'); - video.classList.add('z-depth-1'); + const canvas = document.createElement('canvas'); + canvas.width = canvas.height = doc.w/* * window.devicePixelRatio */; - onVideoLoad(video).then(() => { - if(globalVideo.currentTime !== globalVideo.duration) { - video.currentTime = globalVideo.currentTime; - } - - if(!globalVideo.paused) { - // с закоментированными настройками - хром выключал видео при скролле, для этого нужно было включить видео - выйти из диалога, зайти заново и проскроллить вверх - //video.autoplay = true; - //video.loop = false; - video.play(); - } - }); + 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 = () => { - //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); - globalVideo.removeEventListener('timeupdate', onGlobalTimeUpdate); - globalVideo.removeEventListener('play', onGlobalPlay); - globalVideo.removeEventListener('pause', onGlobalPause); - video.removeEventListener('play', onVideoPlay); - video.removeEventListener('pause', onVideoPause); + const offset = circumference - globalVideo.currentTime / globalVideo.duration * circumference; + circle.style.strokeDashoffset = '' + offset; + + return !globalVideo.paused; }; - const onGlobalTimeUpdate = (e: Event) => { - //console.log('video global timeupdate event', e, globalVideo.currentTime, globalVideo.duration); - if(!isInDOM(video)) { + const onTimeUpdate = () => { + if(!globalVideo.duration) return; + + if(!isInDOM(globalVideo)) { clear(); + return; } + + spanTime.innerText = (globalVideo.duration - globalVideo.currentTime + '').toHHMMSS(false); }; - const onGlobalPlay = (e: Event) => { - //console.log('video global play event', e); - video.play(); + const onPlay = () => { + video.remove(); + divRound.classList.remove('is-paused'); + animateSingle(onFrame, canvas); }; - const onGlobalPause = (e: Event) => { - //console.trace('video global pause event', e, globalVideo.paused, e.eventPhase); - video.pause(); + const onPaused = () => { + if(!isInDOM(globalVideo)) { + clear(); + return; + } + + divRound.classList.add('is-paused'); }; - const onVideoPlay = (e: Event) => { - //console.log('video play event', e); + globalVideo.addEventListener('play', onPlay); + globalVideo.addEventListener('timeupdate', onTimeUpdate); + globalVideo.addEventListener('pause', onPaused); + + attachClickEvent(canvas, (e) => { + cancelEvent(e); + if(globalVideo.paused) { - globalVideo.currentTime = video.currentTime; globalVideo.play(); + } else { + globalVideo.pause(); } - }; + }); - // * this will fire when video unmounts - const onVideoPause = (e: Event) => { - //console.trace('video pause event', e); - if(isInDOM(video)) { - globalVideo.pause(); - globalVideo.currentTime = video.currentTime; + if(globalVideo.paused) { + if(globalVideo.duration && globalVideo.currentTime !== globalVideo.duration) { + onFrame(); + onTimeUpdate(); } else { - clear(); + onPaused(); } - }; - - globalVideo.addEventListener('timeupdate', onGlobalTimeUpdate); - globalVideo.addEventListener('play', onGlobalPlay); - globalVideo.addEventListener('pause', onGlobalPause); - video.addEventListener('play', onVideoPlay); - video.addEventListener('pause', onVideoPause); + } else { + onPlay(); + } } else { video.autoplay = true; // для safari } @@ -276,7 +305,7 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai /* if(!video.paused) { video.pause(); } */ - if(doc.type !== 'round' && group) { + if(group) { animationIntersector.addAnimation(video, group); } @@ -297,24 +326,16 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai deferred.resolve(); }); - if(doc.type !== 'round') { - video.muted = true; - video.loop = true; - //video.play(); - video.autoplay = true; - } + video.muted = true; + video.loop = true; + //video.play(); + video.autoplay = true; 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) { let downloadDiv = document.createElement('div'); downloadDiv.classList.add('download'); diff --git a/src/lib/mediaPlayer.ts b/src/lib/mediaPlayer.ts index aebd680d..368c6320 100644 --- a/src/lib/mediaPlayer.ts +++ b/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 { isAppleMobile } from "../helpers/userAgent"; import { isTouchSupported } from "../helpers/touchSupport"; import RangeSelector from "../components/rangeSelector"; -import { animateSingle } from "../helpers/animation"; import { onVideoLoad } from "../helpers/files"; type SUPEREVENT = MouseEvent | TouchEvent; @@ -358,56 +357,6 @@ export default class VideoPlayer { video.addEventListener('timeupdate', () => { 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 = '
'; - - 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', () => { @@ -465,12 +414,6 @@ export default class VideoPlayer { `; - } else if(skin === 'circle') { - return ` - - - - `; } } diff --git a/src/scss/partials/_chatBubble.scss b/src/scss/partials/_chatBubble.scss index aaf0a2e2..0c6633d5 100644 --- a/src/scss/partials/_chatBubble.scss +++ b/src/scss/partials/_chatBubble.scss @@ -520,12 +520,13 @@ $bubble-margin: .25rem; max-width: 200px !important; max-height: 200px !important; - img { + .media-photo, .media-video { border-radius: 50%; + pointer-events: none; } } } - + &:not(.is-message-empty) .attachment/* , &:not(.is-message-empty).is-reply .attachment */ { border-bottom-left-radius: 0; @@ -1239,6 +1240,7 @@ $bubble-margin: .25rem; align-items: center; cursor: pointer; user-select: none; + height: 1.125rem; &.can-autoplay:after { content: $tgico-nosound; diff --git a/src/scss/partials/_ckin.scss b/src/scss/partials/_ckin.scss index 3181e0b8..e1b568c0 100644 --- a/src/scss/partials/_ckin.scss +++ b/src/scss/partials/_ckin.scss @@ -402,52 +402,3 @@ input[type=range] { justify-content: space-between; 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; - } -} diff --git a/src/scss/style.scss b/src/scss/style.scss index fe1aaf4e..ebafdc17 100644 --- a/src/scss/style.scss +++ b/src/scss/style.scss @@ -1142,7 +1142,7 @@ middle-ellipsis-element { fill: rgba(0, 0, 0, .08); } -.media-photo, .media-video, .media-sticker { +.media-photo, .media-video, .media-sticker, .media-round { position: absolute; top: 0; right: 0; @@ -1165,3 +1165,43 @@ middle-ellipsis-element { .media-sticker { 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; + } + } +}