Media viewer improvements:
Resizing content Mobile video player Minor fixes
This commit is contained in:
parent
5c5cd2d1f4
commit
2d6b25a0a4
@ -144,7 +144,6 @@
|
||||
<div class="overlays">
|
||||
<div class="media-viewer">
|
||||
<div class="media-viewer-author">
|
||||
<div class="btn-icon tgico-close only-handhelds menu-mobile-close rp"></div>
|
||||
<avatar-element class="media-viewer-userpic"></avatar-element>
|
||||
<div class="media-viewer-name"></div>
|
||||
<div class="media-viewer-date"></div>
|
||||
@ -164,6 +163,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-icon tgico-close only-handhelds menu-mobile-close rp"></div>
|
||||
<div class="btn-icon tgico-more rp btn-menu-toggle only-handhelds">
|
||||
<div class="btn-menu bottom-left">
|
||||
<div class="btn-menu-item menu-menu-forward tgico-forward rp">Forward</div>
|
||||
|
@ -18,6 +18,7 @@ import appMediaPlaybackController from "../../components/appMediaPlaybackControl
|
||||
|
||||
// TODO: масштабирование картинок (не SVG) при ресайзе, и правильный возврат на исходную позицию
|
||||
// TODO: картинки "обрезаются" если возвращаются или появляются с места, где есть их перекрытие (топбар, поле ввода)
|
||||
// TODO: видео в мобильной вёрстке, если показываются элементы управления: если свайпнуть в сторону, то элементы вернутся на место, т.е. прыгнут - это не ок, надо бы замаскировать
|
||||
|
||||
class SwipeHandler {
|
||||
private xDown: number;
|
||||
@ -205,6 +206,9 @@ export class AppMediaViewer {
|
||||
|
||||
if(touchSupport) {
|
||||
const swipeHandler = new SwipeHandler(this.wholeDiv, (xDiff, yDiff) => {
|
||||
if(VideoPlayer.isFullScreen()) {
|
||||
return;
|
||||
}
|
||||
//console.log(xDiff, yDiff);
|
||||
|
||||
const percents = Math.abs(xDiff) / appPhotosManager.windowW;
|
||||
@ -358,7 +362,7 @@ export class AppMediaViewer {
|
||||
} */
|
||||
|
||||
let aspecter: HTMLDivElement;
|
||||
if(target instanceof HTMLImageElement || target instanceof HTMLVideoElement) {
|
||||
if(target instanceof HTMLImageElement || target instanceof HTMLVideoElement || target.tagName == 'DIV') {
|
||||
if(mover.firstElementChild && mover.firstElementChild.classList.contains('media-viewer-aspecter')) {
|
||||
aspecter = mover.firstElementChild as HTMLDivElement;
|
||||
|
||||
@ -491,18 +495,29 @@ export class AppMediaViewer {
|
||||
|
||||
if(aspecter) {
|
||||
aspecter.style.borderRadius = borderRadius;
|
||||
aspecter.append(mediaElement);
|
||||
|
||||
if(mediaElement) {
|
||||
aspecter.append(mediaElement);
|
||||
}
|
||||
}
|
||||
|
||||
mediaElement = mover.querySelector('video, img');
|
||||
if(mediaElement instanceof HTMLImageElement && src) {
|
||||
await new Promise((resolve, reject) => {
|
||||
mediaElement.addEventListener('load', resolve);
|
||||
if(mediaElement instanceof HTMLImageElement) {
|
||||
mediaElement.classList.add('thumbnail');
|
||||
if(!aspecter) {
|
||||
mediaElement.style.width = containerRect.width + 'px';
|
||||
mediaElement.style.height = containerRect.height + 'px';
|
||||
}
|
||||
|
||||
if(src) {
|
||||
mediaElement.src = src;
|
||||
}
|
||||
});
|
||||
if(src) {
|
||||
await new Promise((resolve, reject) => {
|
||||
mediaElement.addEventListener('load', resolve);
|
||||
|
||||
if(src) {
|
||||
mediaElement.src = src;
|
||||
}
|
||||
});
|
||||
}
|
||||
}/* else if(mediaElement instanceof HTMLVideoElement && mediaElement.firstElementChild && ((mediaElement.firstElementChild as HTMLSourceElement).src || src)) {
|
||||
await new Promise((resolve, reject) => {
|
||||
mediaElement.addEventListener('loadeddata', resolve);
|
||||
@ -551,7 +566,7 @@ export class AppMediaViewer {
|
||||
setTimeout(() => {
|
||||
mover.innerHTML = '';
|
||||
mover.classList.remove('moving', 'active', 'hiding');
|
||||
mover.style.display = 'none';
|
||||
mover.style.cssText = 'display: none;';
|
||||
|
||||
deferred.resolve();
|
||||
}, delay);
|
||||
@ -669,7 +684,8 @@ export class AppMediaViewer {
|
||||
|
||||
private removeCenterFromMover(mover: HTMLDivElement) {
|
||||
if(mover.classList.contains('center')) {
|
||||
const rect = mover.getBoundingClientRect();
|
||||
//const rect = mover.getBoundingClientRect();
|
||||
const rect = this.content.container.getBoundingClientRect();
|
||||
mover.style.transform = `translate(${rect.left}px,${rect.top}px)`;
|
||||
mover.classList.remove('center');
|
||||
void mover.offsetLeft; // reflow
|
||||
@ -818,7 +834,7 @@ export class AppMediaViewer {
|
||||
this.log('openMedia doc:', message);
|
||||
const media = message.media.photo || message.media.document || message.media.webpage.document || message.media.webpage.photo;
|
||||
|
||||
const isVideo = (media as MTDocument).type == 'video';
|
||||
const isVideo = (media as MTDocument).type == 'video' || (media as MTDocument).type == 'gif';
|
||||
const isFirstOpen = !this.peerID;
|
||||
|
||||
if(isFirstOpen) {
|
||||
@ -946,7 +962,11 @@ export class AppMediaViewer {
|
||||
//video.src = '';
|
||||
|
||||
video.setAttribute('playsinline', '');
|
||||
video.autoplay = true;
|
||||
|
||||
if(isSafari) {
|
||||
video.autoplay = true;
|
||||
}
|
||||
|
||||
if(media.type == 'gif') {
|
||||
video.muted = true;
|
||||
video.autoplay = true;
|
||||
@ -958,7 +978,7 @@ export class AppMediaViewer {
|
||||
}
|
||||
|
||||
const canPlayThrough = new Promise((resolve) => {
|
||||
video.addEventListener('canplaythrough', resolve, {once: true});
|
||||
video.addEventListener('canplay', resolve, {once: true});
|
||||
});
|
||||
|
||||
const createPlayer = () => {
|
||||
@ -1039,29 +1059,9 @@ export class AppMediaViewer {
|
||||
|
||||
this.updateMediaSource(mover, url, 'video');
|
||||
} else {
|
||||
//const promise = new Promise((resolve) => video.addEventListener('loadeddata', resolve, {once: true}));
|
||||
renderImageFromUrl(video, url);
|
||||
|
||||
//await promise;
|
||||
/* const first = div.firstElementChild as HTMLImageElement;
|
||||
if(!(first instanceof HTMLVideoElement) && first) {
|
||||
first.remove();
|
||||
}
|
||||
|
||||
if(!video.parentElement) {
|
||||
div.prepend(video);
|
||||
} */
|
||||
}
|
||||
|
||||
// я хз что это такое, видео появляются просто чёрными и не включаются без этого кода снизу
|
||||
/* source.remove();
|
||||
window.requestAnimationFrame(() => {
|
||||
window.requestAnimationFrame(() => {
|
||||
//parent.append(video);
|
||||
video.append(source);
|
||||
});
|
||||
}); */
|
||||
|
||||
createPlayer();
|
||||
});
|
||||
|
||||
@ -1081,8 +1081,8 @@ export class AppMediaViewer {
|
||||
//if(wasActive) return;
|
||||
//return;
|
||||
|
||||
let load = () => {
|
||||
let cancellablePromise = appPhotosManager.preloadPhoto(media.id, size);
|
||||
const load = () => {
|
||||
const cancellablePromise = appPhotosManager.preloadPhoto(media.id, size);
|
||||
onAnimationEnd.then(() => {
|
||||
this.preloader.attach(mover, true, cancellablePromise);
|
||||
});
|
||||
@ -1094,12 +1094,19 @@ export class AppMediaViewer {
|
||||
|
||||
///////this.log('indochina', blob);
|
||||
|
||||
let url = media.url;
|
||||
const url = media.url;
|
||||
if(target instanceof SVGSVGElement) {
|
||||
this.updateMediaSource(target, url, 'img');
|
||||
this.updateMediaSource(mover, url, 'img');
|
||||
|
||||
/* const imgs = mover.querySelectorAll('img');
|
||||
if(imgs && imgs.length) {
|
||||
imgs.forEach(img => {
|
||||
img.classList.remove('thumbnail'); // может здесь это вообще не нужно
|
||||
});
|
||||
} */
|
||||
} else {
|
||||
let div = mover.firstElementChild && mover.firstElementChild.classList.contains('media-viewer-aspecter') ? mover.firstElementChild : mover;
|
||||
const div = mover.firstElementChild && mover.firstElementChild.classList.contains('media-viewer-aspecter') ? mover.firstElementChild : mover;
|
||||
let image = div.firstElementChild as HTMLImageElement;
|
||||
if(!image || image.tagName != 'IMG') {
|
||||
image = new Image();
|
||||
@ -1108,6 +1115,7 @@ export class AppMediaViewer {
|
||||
//this.log('will renderImageFromUrl:', image, div, target);
|
||||
|
||||
renderImageFromUrl(image, url, () => {
|
||||
image.classList.remove('thumbnail'); // может здесь это вообще не нужно
|
||||
div.append(image);
|
||||
});
|
||||
}
|
||||
|
@ -226,20 +226,24 @@ export class AppSidebarRight extends SidebarSlider {
|
||||
});
|
||||
|
||||
this.sharedMedia.contentMedia.addEventListener('click', (e) => {
|
||||
let target = e.target as HTMLDivElement;
|
||||
const target = e.target as HTMLDivElement;
|
||||
|
||||
let messageID = +target.dataset.mid;
|
||||
const messageID = +target.dataset.mid;
|
||||
if(!messageID) {
|
||||
this.log.warn('no messageID by click on target:', target);
|
||||
return;
|
||||
}
|
||||
|
||||
let message = appMessagesManager.getMessage(messageID);
|
||||
const message = appMessagesManager.getMessage(messageID);
|
||||
|
||||
let ids = Object.keys(this.mediaDivsByIDs).map(k => +k).sort((a, b) => a - b);
|
||||
let idx = ids.findIndex(i => i == messageID);
|
||||
const ids = Object.keys(this.mediaDivsByIDs).map(k => +k).sort((a, b) => a - b);
|
||||
const idx = ids.findIndex(i => i == messageID);
|
||||
|
||||
let targets = ids.map(id => ({element: this.mediaDivsByIDs[id]/* .firstElementChild */ as HTMLElement, mid: id}));
|
||||
const targets = ids.map(id => {
|
||||
const element = this.mediaDivsByIDs[id] as HTMLElement;
|
||||
//element = element.querySelector('img') || element;
|
||||
return {element, mid: id};
|
||||
});
|
||||
|
||||
appMediaViewer.openMedia(message, target, false, this.sidebarEl, targets.slice(idx + 1).reverse(), targets.slice(0, idx).reverse(), true);
|
||||
});
|
||||
|
@ -2,6 +2,8 @@ import { cancelEvent } from "./utils";
|
||||
import { touchSupport } from "./config";
|
||||
import appMediaPlaybackController from "../components/appMediaPlaybackController";
|
||||
|
||||
type SUPEREVENT = MouseEvent | TouchEvent;
|
||||
|
||||
export class ProgressLine {
|
||||
public container: HTMLDivElement;
|
||||
protected filled: HTMLDivElement;
|
||||
@ -46,17 +48,17 @@ export class ProgressLine {
|
||||
this.events = events;
|
||||
}
|
||||
|
||||
onMouseMove = (e: MouseEvent) => {
|
||||
onMouseMove = (e: SUPEREVENT) => {
|
||||
this.mousedown && this.scrub(e);
|
||||
};
|
||||
|
||||
onMouseDown = (e: MouseEvent) => {
|
||||
onMouseDown = (e: SUPEREVENT) => {
|
||||
this.scrub(e);
|
||||
this.mousedown = true;
|
||||
this.events?.onMouseDown && this.events.onMouseDown(e);
|
||||
};
|
||||
|
||||
onMouseUp = (e: MouseEvent) => {
|
||||
onMouseUp = (e: SUPEREVENT) => {
|
||||
this.mousedown = false;
|
||||
this.events?.onMouseUp && this.events.onMouseUp(e);
|
||||
};
|
||||
@ -65,6 +67,12 @@ export class ProgressLine {
|
||||
this.container.addEventListener('mousemove', this.onMouseMove);
|
||||
this.container.addEventListener('mousedown', this.onMouseDown);
|
||||
this.container.addEventListener('mouseup', this.onMouseUp);
|
||||
|
||||
if(touchSupport) {
|
||||
this.container.addEventListener('touchmove', this.onMouseMove);
|
||||
this.container.addEventListener('touchstart', this.onMouseDown);
|
||||
this.container.addEventListener('touchend', this.onMouseUp);
|
||||
}
|
||||
}
|
||||
|
||||
public setProgress(scrubTime: number) {
|
||||
@ -78,8 +86,16 @@ export class ProgressLine {
|
||||
this.filled.style.transform = 'scaleX(' + scaleX + ')';
|
||||
}
|
||||
|
||||
protected scrub(e: MouseEvent) {
|
||||
const scrubTime = e.offsetX / this.container.offsetWidth * this.duration;
|
||||
protected scrub(e: SUPEREVENT) {
|
||||
let offsetX: number;
|
||||
if(e instanceof TouchEvent) {
|
||||
const rect = (e.target as HTMLElement).getBoundingClientRect();
|
||||
offsetX = e.targetTouches[0].pageX - rect.left;
|
||||
} else {
|
||||
offsetX = e.offsetX;
|
||||
}
|
||||
|
||||
const scrubTime = offsetX / this.container.offsetWidth * this.duration;
|
||||
|
||||
this.setFilled(scrubTime);
|
||||
|
||||
@ -92,6 +108,12 @@ export class ProgressLine {
|
||||
this.container.removeEventListener('mousedown', this.onMouseDown);
|
||||
this.container.removeEventListener('mouseup', this.onMouseUp);
|
||||
|
||||
if(touchSupport) {
|
||||
this.container.removeEventListener('touchmove', this.onMouseMove);
|
||||
this.container.removeEventListener('touchstart', this.onMouseDown);
|
||||
this.container.removeEventListener('touchend', this.onMouseUp);
|
||||
}
|
||||
|
||||
this.events = {};
|
||||
}
|
||||
}
|
||||
@ -119,7 +141,7 @@ export class MediaProgressLine extends ProgressLine {
|
||||
this.setSeekMax();
|
||||
this.setListeners();
|
||||
this.setHandlers({
|
||||
onMouseDown: (e: MouseEvent) => {
|
||||
onMouseDown: (e: SUPEREVENT) => {
|
||||
//super.onMouseDown(e);
|
||||
|
||||
//Таймер для того, чтобы стопать видео, если зажал мышку и не отпустил клик
|
||||
@ -133,7 +155,7 @@ export class MediaProgressLine extends ProgressLine {
|
||||
}, 150);
|
||||
},
|
||||
|
||||
onMouseUp: (e: MouseEvent) => {
|
||||
onMouseUp: (e: SUPEREVENT) => {
|
||||
//super.onMouseUp(e);
|
||||
|
||||
if(this.stopAndScrubTimeout) {
|
||||
@ -379,8 +401,41 @@ export default class VideoPlayer {
|
||||
video.addEventListener('click', () => {
|
||||
if(!touchSupport) {
|
||||
this.togglePlay();
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
if(touchSupport) {
|
||||
let showControlsTimeout = 0;
|
||||
|
||||
const t = () => {
|
||||
showControlsTimeout = setTimeout(() => {
|
||||
showControlsTimeout = 0;
|
||||
player.classList.remove('show-controls');
|
||||
}, 3e3);
|
||||
};
|
||||
|
||||
player.addEventListener('click', () => {
|
||||
if(showControlsTimeout) {
|
||||
clearTimeout(showControlsTimeout);
|
||||
} else {
|
||||
player.classList.add('show-controls');
|
||||
}
|
||||
|
||||
t();
|
||||
});
|
||||
|
||||
player.addEventListener('touchstart', () => {
|
||||
player.classList.add('show-controls');
|
||||
clearTimeout(showControlsTimeout);
|
||||
});
|
||||
|
||||
player.addEventListener('touchend', () => {
|
||||
if(player.classList.contains('is-playing')) {
|
||||
t();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* player.addEventListener('click', (e) => {
|
||||
if(e.target != player) {
|
||||
@ -398,6 +453,10 @@ export default class VideoPlayer {
|
||||
});
|
||||
|
||||
video.addEventListener('dblclick', () => {
|
||||
if(touchSupport) {
|
||||
return;
|
||||
}
|
||||
|
||||
return this.toggleFullScreen(fullScreenButton);
|
||||
})
|
||||
|
||||
@ -540,13 +599,17 @@ export default class VideoPlayer {
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
public static isFullScreen(): boolean {
|
||||
// @ts-ignore
|
||||
return !!(document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || document.msFullscreenElement);
|
||||
}
|
||||
|
||||
public toggleFullScreen(fullScreenButton: HTMLElement) {
|
||||
// alternative standard method
|
||||
const player = this.wrapper;
|
||||
|
||||
// @ts-ignore
|
||||
if(!document.fullscreenElement && !document.mozFullScreenElement && !document.webkitFullscreenElement && !document.msFullscreenElement) {
|
||||
if(!VideoPlayer.isFullScreen()) {
|
||||
player.classList.add('ckin__fullscreen');
|
||||
|
||||
/* const videoParent = this.video.parentElement;
|
||||
|
@ -17,8 +17,10 @@
|
||||
display: flex;
|
||||
|
||||
video {
|
||||
max-height: none;
|
||||
max-width: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
/* max-height: none;
|
||||
max-width: none; */
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
@ -45,6 +47,9 @@
|
||||
//overflow: hidden;
|
||||
//border-radius: 5px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
@ -168,7 +173,8 @@
|
||||
transform: translateY(50px);
|
||||
}
|
||||
|
||||
html.no-touch &:hover {
|
||||
html.no-touch &:hover,
|
||||
&.show-controls {
|
||||
.default__gradient-bottom {
|
||||
transform: translateY(0px);
|
||||
}
|
||||
@ -204,6 +210,10 @@
|
||||
margin: -3px 2px 0 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@include respond-to(handhelds) {
|
||||
margin-right: .75rem;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
fill: #fff;
|
||||
|
@ -196,8 +196,18 @@ $move-duration: .35s;
|
||||
overflow: hidden;
|
||||
//border-radius: 0;
|
||||
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
// эти значения должны быть такими же, как при установке maxWidth и maxHeight в openMedia!
|
||||
//max-width: 100%;
|
||||
max-width: calc(100% - 16px);
|
||||
//max-height: 100%;
|
||||
max-height: calc(100% - 100px);
|
||||
|
||||
// эти значения должны быть такими же, как при установке maxWidth и maxHeight в openMedia!
|
||||
@include respond-to(handhelds) {
|
||||
overflow: visible;
|
||||
//max-height: 100% !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
.ckin__player {
|
||||
width: 100%;
|
||||
@ -215,8 +225,11 @@ $move-duration: .35s;
|
||||
img, video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
user-select: none;
|
||||
object-fit: cover;
|
||||
//object-fit: contain;
|
||||
opacity: 1;
|
||||
|
||||
//&.thumbnail {
|
||||
@ -245,6 +258,55 @@ $move-duration: .35s;
|
||||
left: 50% !important;
|
||||
top: 50% !important;
|
||||
transform: translate(-50%, -50%) !important;
|
||||
|
||||
@include respond-to(handhelds) {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
//height: calc(100% - 100px) !important;
|
||||
/* height: calc(100% - 50px) !important;
|
||||
top: calc(50% + 25px) !important;
|
||||
|
||||
img, video {
|
||||
margin-top: -25px;
|
||||
} */
|
||||
|
||||
/* img, video {
|
||||
max-height: calc(100% - 100px);
|
||||
} */
|
||||
|
||||
.ckin__player:not(.ckin__fullscreen) {
|
||||
.default__controls {
|
||||
bottom: -50px;
|
||||
}
|
||||
|
||||
.default__gradient-bottom {
|
||||
bottom: -50px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* @include respond-to(handhelds) {
|
||||
&.moving {
|
||||
.ckin__player {
|
||||
.default__controls, .default__gradient-bottom {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
} */
|
||||
|
||||
img:not(.thumbnail), video {
|
||||
height: auto;
|
||||
width: auto;
|
||||
object-fit: contain;
|
||||
//max-height: calc(100% - 100px);
|
||||
}
|
||||
|
||||
img.thumbnail {
|
||||
width: auto;
|
||||
object-fit: contain;
|
||||
//height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
&.hiding {
|
||||
@ -262,6 +324,10 @@ $move-duration: .35s;
|
||||
transform: scale(1);
|
||||
//overflow: hidden; // WARNING
|
||||
position: absolute;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&-mover.active &-aspecter {
|
||||
@ -283,7 +349,7 @@ $move-duration: .35s;
|
||||
visibility: visible;
|
||||
transition-delay: 0s;
|
||||
|
||||
.overlays, .btn-menu-toggle {
|
||||
.overlays, > .btn-icon {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transition: opacity $open-duration 0s, visibility 0s 0s;
|
||||
@ -292,18 +358,19 @@ $move-duration: .35s;
|
||||
|
||||
@include respond-to(handhelds) {
|
||||
.menu-mobile-close {
|
||||
position: absolute;
|
||||
left: 20px;
|
||||
top: 8px;
|
||||
}
|
||||
|
||||
.btn-menu-toggle {
|
||||
position: fixed;
|
||||
right: 8px;
|
||||
|
||||
> .btn-icon {
|
||||
top: 8px;
|
||||
position: fixed;
|
||||
z-index: 5;
|
||||
opacity: 0;
|
||||
transition: opacity $open-duration 0s, visibility 0s $open-duration;
|
||||
}
|
||||
|
||||
.btn-menu-toggle {
|
||||
right: 8px;
|
||||
|
||||
&.menu-open {
|
||||
color: #fff;
|
||||
|
@ -1224,6 +1224,7 @@ input:focus, button:focus {
|
||||
img.emoji {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
margin: 0 .125rem;
|
||||
}
|
||||
|
||||
.btn-circle {
|
||||
|
Loading…
x
Reference in New Issue
Block a user