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. 54
      src/scss/partials/_ckin.scss

8
src/components/appMediaViewer.ts

@ -93,6 +93,8 @@ class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType @@ -93,6 +93,8 @@ class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType
protected onPrevClick: (target: TargetType) => void;
protected onNextClick: (target: TargetType) => void;
protected loadMoreMedia: (older: boolean) => Promise<void>;
protected videoPlayer: VideoPlayer;
constructor(topButtons: Array<keyof AppMediaViewerBase<ContentAdditionType, ButtonsAdditionType, TargetType>['buttons']>) {
this.log = logger('AMV');
@ -336,6 +338,11 @@ class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType @@ -336,6 +338,11 @@ class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType
};
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;
if(!closing) {
@ -1055,6 +1062,7 @@ class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType @@ -1055,6 +1062,7 @@ class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType
}
const player = new VideoPlayer(video, true, media.supportsStreaming);
this.videoPlayer = player;
/* div.append(video);
mover.append(player.wrapper); */
});

126
src/lib/mediaPlayer.ts

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

54
src/scss/partials/_ckin.scss

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

Loading…
Cancel
Save