Browse Source

Video player fixes:

Hide controls & cursor in fullscreen mode
Fix flickering pause on start
F, M, Space keyboard shortcuts
master
Eduard Kuzmenko 3 years ago
parent
commit
2c86501ec5
  1. 8
      src/components/appMediaViewer.ts
  2. 126
      src/lib/mediaPlayer.ts
  3. 52
      src/scss/partials/_ckin.scss

8
src/components/appMediaViewer.ts

@ -94,6 +94,8 @@ class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType
protected onNextClick: (target: TargetType) => void; protected onNextClick: (target: TargetType) => void;
protected loadMoreMedia: (older: boolean) => Promise<void>; protected loadMoreMedia: (older: boolean) => Promise<void>;
protected videoPlayer: VideoPlayer;
constructor(topButtons: Array<keyof AppMediaViewerBase<ContentAdditionType, ButtonsAdditionType, TargetType>['buttons']>) { constructor(topButtons: Array<keyof AppMediaViewerBase<ContentAdditionType, ButtonsAdditionType, TargetType>['buttons']>) {
this.log = logger('AMV'); this.log = logger('AMV');
this.preloader = new ProgressivePreloader(); this.preloader = new ProgressivePreloader();
@ -336,6 +338,11 @@ class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType
}; };
protected async setMoverToTarget(target: HTMLElement, closing = false, fromRight = 0) { protected async setMoverToTarget(target: HTMLElement, closing = false, fromRight = 0) {
if(this.videoPlayer) { // there could be a better place for it
this.videoPlayer.removeListeners();
this.videoPlayer = undefined;
}
const mover = this.content.mover; const mover = this.content.mover;
if(!closing) { if(!closing) {
@ -1055,6 +1062,7 @@ class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType
} }
const player = new VideoPlayer(video, true, media.supportsStreaming); const player = new VideoPlayer(video, true, media.supportsStreaming);
this.videoPlayer = player;
/* div.append(video); /* div.append(video);
mover.append(player.wrapper); */ mover.append(player.wrapper); */
}); });

126
src/lib/mediaPlayer.ts

@ -10,6 +10,7 @@ import { isTouchSupported } from "../helpers/touchSupport";
import RangeSelector from "../components/rangeSelector"; import RangeSelector from "../components/rangeSelector";
import { onVideoLoad } from "../helpers/files"; import { onVideoLoad } from "../helpers/files";
import { cancelEvent } from "../helpers/dom/cancelEvent"; import { cancelEvent } from "../helpers/dom/cancelEvent";
import ListenerSetter from "../helpers/listenerSetter";
export class MediaProgressLine extends RangeSelector { export class MediaProgressLine extends RangeSelector {
private filledLoad: HTMLDivElement; private filledLoad: HTMLDivElement;
@ -165,17 +166,21 @@ export class MediaProgressLine extends RangeSelector {
let lastVolume = 1, muted = !lastVolume; let lastVolume = 1, muted = !lastVolume;
export default class VideoPlayer { export default class VideoPlayer {
public wrapper: HTMLDivElement; private wrapper: HTMLDivElement;
public progress: MediaProgressLine; private progress: MediaProgressLine;
private skin: string; private skin: string;
private listenerSetter: ListenerSetter;
/* private videoParent: HTMLElement; /* private videoParent: HTMLElement;
private videoWhichChild: number; */ private videoWhichChild: number; */
constructor(public video: HTMLVideoElement, play = false, streamable = false, duration?: number) { constructor(private 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');
this.listenerSetter = new ListenerSetter();
video.parentNode.insertBefore(this.wrapper, video); video.parentNode.insertBefore(this.wrapper, video);
this.wrapper.appendChild(video); this.wrapper.appendChild(video);
@ -184,7 +189,7 @@ export default class VideoPlayer {
this.stylePlayer(duration); this.stylePlayer(duration);
if(this.skin === 'default') { if(this.skin === 'default') {
let controls = this.wrapper.querySelector('.default__controls.ckin__controls') as HTMLDivElement; const controls = this.wrapper.querySelector('.default__controls.ckin__controls') as HTMLDivElement;
this.progress = new MediaProgressLine(video, streamable); this.progress = new MediaProgressLine(video, streamable);
controls.prepend(this.progress.container); controls.prepend(this.progress.container);
} }
@ -228,10 +233,12 @@ export default class VideoPlayer {
`; `;
const volumeSvg = volumeDiv.firstElementChild as SVGSVGElement; const volumeSvg = volumeDiv.firstElementChild as SVGSVGElement;
volumeSvg.addEventListener('click', (e) => { const onMuteClick = (e?: Event) => {
cancelEvent(e); e && cancelEvent(e);
video.muted = !video.muted; video.muted = !video.muted;
}); };
this.listenerSetter.add(volumeSvg, 'click', onMuteClick);
const volumeProgress = new RangeSelector(0.01, 1, 0, 1); const volumeProgress = new RangeSelector(0.01, 1, 0, 1);
volumeProgress.setListeners(); volumeProgress.setListeners();
@ -270,7 +277,7 @@ export default class VideoPlayer {
}; };
// не вызовется повторно если на 1 установить 1 // не вызовется повторно если на 1 установить 1
video.addEventListener('volumechange', () => { this.listenerSetter.add(video, 'volumechange', () => {
muted = video.muted; muted = video.muted;
lastVolume = video.volume; lastVolume = video.volume;
setVolume(); setVolume();
@ -287,46 +294,56 @@ export default class VideoPlayer {
leftControls.insertBefore(volumeDiv, timeElapsed.parentElement); leftControls.insertBefore(volumeDiv, timeElapsed.parentElement);
Array.from(toggle).forEach((button) => { Array.from(toggle).forEach((button) => {
return button.addEventListener('click', () => { this.listenerSetter.add(button, 'click', () => {
this.togglePlay(); this.togglePlay();
}); });
}); });
video.addEventListener('click', () => { this.listenerSetter.add(video, 'click', () => {
if(!isTouchSupported) { if(!isTouchSupported) {
this.togglePlay(); this.togglePlay();
return;
} }
}); });
if(isTouchSupported) { let showControlsTimeout = 0;
let showControlsTimeout = 0;
const t = () => {
showControlsTimeout = window.setTimeout(() => {
showControlsTimeout = 0;
player.classList.remove('show-controls');
}, 3e3);
};
player.addEventListener('click', () => {
if(showControlsTimeout) {
clearTimeout(showControlsTimeout);
} else {
player.classList.add('show-controls');
}
t(); const showControls = () => {
if(showControlsTimeout) clearTimeout(showControlsTimeout);
else player.classList.add('show-controls');
showControlsTimeout = window.setTimeout(() => {
showControlsTimeout = 0;
player.classList.remove('show-controls');
}, 3e3);
};
if(isTouchSupported) {
this.listenerSetter.add(player, 'click', () => {
showControls();
}); });
player.addEventListener('touchstart', () => { this.listenerSetter.add(player, 'touchstart', () => {
player.classList.add('show-controls'); player.classList.add('show-controls');
clearTimeout(showControlsTimeout); clearTimeout(showControlsTimeout);
}); });
player.addEventListener('touchend', () => { this.listenerSetter.add(player, 'touchend', () => {
if(player.classList.contains('is-playing')) { if(player.classList.contains('is-playing')) {
t(); showControls();
}
});
} else {
this.listenerSetter.add(this.wrapper, 'mousemove', () => {
showControls();
});
this.listenerSetter.add(document, 'keydown', (e: KeyboardEvent) => {
if(e.code === 'KeyF') {
this.toggleFullScreen(fullScreenButton);
} else if(e.code === 'KeyM') {
onMuteClick();
} else if(e.code === 'Space') {
this.togglePlay();
} }
}); });
} }
@ -342,32 +359,34 @@ export default class VideoPlayer {
/* video.addEventListener('play', () => { /* video.addEventListener('play', () => {
}); */ }); */
video.addEventListener('dblclick', () => { this.listenerSetter.add(video, 'dblclick', () => {
if(isTouchSupported) { if(!isTouchSupported) {
return; this.toggleFullScreen(fullScreenButton);
} }
});
return this.toggleFullScreen(fullScreenButton); this.listenerSetter.add(fullScreenButton, 'click', (e) => {
}) this.toggleFullScreen(fullScreenButton);
fullScreenButton.addEventListener('click', (e) => {
return this.toggleFullScreen(fullScreenButton);
}); });
'webkitfullscreenchange mozfullscreenchange fullscreenchange MSFullscreenChange'.split(' ').forEach(eventName => { 'webkitfullscreenchange mozfullscreenchange fullscreenchange MSFullscreenChange'.split(' ').forEach(eventName => {
player.addEventListener(eventName, this.onFullScreen, false); this.listenerSetter.add(player, eventName, this.onFullScreen, false);
}); });
video.addEventListener('timeupdate', () => { this.listenerSetter.add(video, 'timeupdate', () => {
timeElapsed.innerHTML = String(video.currentTime | 0).toHHMMSS(); timeElapsed.innerHTML = String(video.currentTime | 0).toHHMMSS();
}); });
this.listenerSetter.add(video, 'play', () => {
this.wrapper.classList.add('played');
}, {once: true});
} }
video.addEventListener('play', () => { this.listenerSetter.add(video, 'play', () => {
this.wrapper.classList.add('is-playing'); this.wrapper.classList.add('is-playing');
}); });
video.addEventListener('pause', () => { this.listenerSetter.add(video, 'pause', () => {
this.wrapper.classList.remove('is-playing'); this.wrapper.classList.remove('is-playing');
}); });
@ -380,21 +399,8 @@ export default class VideoPlayer {
} }
} }
public togglePlay(stop?: boolean) { public togglePlay() {
//console.log('video togglePlay:', stop, this.video.paused);
if(stop) {
this.video.pause();
this.wrapper.classList.remove('is-playing');
return;
} else if(stop === false) {
this.video.play();
this.wrapper.classList.add('is-playing');
return;
}
this.video[this.video.paused ? 'play' : 'pause'](); this.video[this.video.paused ? 'play' : 'pause']();
//this.wrapper.classList.toggle('is-playing', !this.video.paused);
} }
private buildControls() { private buildControls() {
@ -514,7 +520,11 @@ export default class VideoPlayer {
const isFullscreenNow = document.webkitFullscreenElement !== null; const isFullscreenNow = document.webkitFullscreenElement !== null;
if(!isFullscreenNow) { if(!isFullscreenNow) {
this.wrapper.classList.remove('ckin__fullscreen'); this.wrapper.classList.remove('ckin__fullscreen');
} else {
} }
}; };
public removeListeners() {
this.listenerSetter.removeAll();
this.progress.removeListeners();
}
} }

52
src/scss/partials/_ckin.scss

@ -64,11 +64,14 @@
font-size: 0; font-size: 0;
//overflow: hidden; //overflow: hidden;
//border-radius: 5px; //border-radius: 5px;
cursor: pointer;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
&.show-controls video {
cursor: pointer;
}
&:before { &:before {
content: ''; content: '';
position: absolute; position: absolute;
@ -123,11 +126,18 @@
left: 50%; left: 50%;
transform: translate3d(-50%, -50%, 0) scale(1); transform: translate3d(-50%, -50%, 0) scale(1);
font-size: 64px; font-size: 64px;
transition: all .2s; transition: visibility .2s, opacity .2s;
touch-action: manipulation; touch-action: manipulation;
} }
} }
&:not(.played) {
.default__button--big {
opacity: 0;
visibility: hidden;
}
}
&__slider { &__slider {
width: 10px; width: 10px;
height: 30px; height: 30px;
@ -138,7 +148,7 @@
bottom: 0; bottom: 0;
right: 0; right: 0;
left: 0; left: 0;
transition: all .3s; transition: transform .3s;
text-align: left; text-align: left;
direction: ltr; direction: ltr;
z-index: 6; z-index: 6;
@ -168,7 +178,7 @@
position: absolute; position: absolute;
background-repeat: repeat-x; background-repeat: repeat-x;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAADGCAYAAAAT+OqFAAAAdklEQVQoz42QQQ7AIAgEF/T/D+kbq/RWAlnQyyazA4aoAB4FsBSA/bFjuF1EOL7VbrIrBuusmrt4ZZORfb6ehbWdnRHEIiITaEUKa5EJqUakRSaEYBJSCY2dEstQY7AuxahwXFrvZmWl2rh4JZ07z9dLtesfNj5q0FU3A5ObbwAAAABJRU5ErkJggg==); background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAADGCAYAAAAT+OqFAAAAdklEQVQoz42QQQ7AIAgEF/T/D+kbq/RWAlnQyyazA4aoAB4FsBSA/bFjuF1EOL7VbrIrBuusmrt4ZZORfb6ehbWdnRHEIiITaEUKa5EJqUakRSaEYBJSCY2dEstQY7AuxahwXFrvZmWl2rh4JZ07z9dLtesfNj5q0FU3A5ObbwAAAABJRU5ErkJggg==);
transition: all .3s; transition: transform .3s;
pointer-events: none; pointer-events: none;
} }
@ -181,17 +191,22 @@
transform: translate3d(0, 50px, 0); transform: translate3d(0, 50px, 0);
} }
html.no-touch &:hover, .default__controls {
&.show-controls { transform: translate3d(0, 52px, 0);
.default__gradient-bottom { }
transform: translateZ(0);
}
.default__controls { /* html.no-touch &:hover,*/
&.show-controls.ckin__fullscreen,
&.show-controls:not(.ckin__fullscreen):hover {
.default__gradient-bottom, .default__controls {
transform: translateZ(0); transform: translateZ(0);
} }
} }
&:not(.show-controls) {
cursor: none;
}
&:before { &:before {
opacity: 0; opacity: 0;
visibility: hidden; visibility: hidden;
@ -203,11 +218,7 @@
visibility: hidden; visibility: hidden;
} }
.default__controls { .toggle:not(.default__button--big) {
transform: translate3d(0, 52px, 0);
}
.toggle {
&:before { &:before {
content: $tgico-pause; content: $tgico-pause;
} }
@ -238,6 +249,7 @@
} }
.progress-line { .progress-line {
--color: #fff;
margin: 0; margin: 0;
width: 50px; width: 50px;
@ -246,19 +258,10 @@
display: none; display: none;
} }
&__filled {
background: #fff;
}
&__seek { &__seek {
--thumb-size: 15px; --thumb-size: 15px;
&::-webkit-slider-thumb {
background: #fff;
}
} }
} }
} }
&.is-buffering { &.is-buffering {
@ -309,6 +312,7 @@ video::-webkit-media-controls-enclosure {
padding: 0; padding: 0;
margin: 0; margin: 0;
outline: none; outline: none;
caret-color: var(--color);
&:focus { &:focus {
outline: none; outline: none;

Loading…
Cancel
Save