implementing video controls
This commit is contained in:
parent
f13f333d49
commit
6ca35f2f20
68
frontend/app/gallery/lightbox/inputrange.css
Normal file
68
frontend/app/gallery/lightbox/inputrange.css
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
input[type="range"] {
|
||||||
|
margin: auto;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
height: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 0; /* iOS */
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"]::-webkit-slider-runnable-track {
|
||||||
|
background: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 1. Set to 0 width and remove border for a slider without a thumb
|
||||||
|
*/
|
||||||
|
input[type="range"]::-webkit-slider-thumb {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
width: 10px; /* 1 */
|
||||||
|
height: 10px;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: -100vw 0 0 100vw white;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"]::-moz-range-track {
|
||||||
|
height: 8px;
|
||||||
|
background: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"]::-moz-range-thumb {
|
||||||
|
background: #fff;
|
||||||
|
height: 10px;
|
||||||
|
width: 10px;
|
||||||
|
box-shadow: -100vw 0 0 100vw white;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"]::-ms-fill-lower {
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"]::-ms-thumb {
|
||||||
|
background: #fff;
|
||||||
|
height: 10px;
|
||||||
|
width: 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"]::-ms-ticks-after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"]::-ms-ticks-before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"]::-ms-track {
|
||||||
|
background: #6c757d;
|
||||||
|
color: transparent;
|
||||||
|
height: 8px;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"]::-ms-tooltip {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
@ -29,8 +29,10 @@ app-gallery-lightbox-photo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.navigation-arrow {
|
.navigation-arrow {
|
||||||
width: 30%;
|
width: 20%;
|
||||||
height: calc(100% - 75px);
|
min-width: 60px;
|
||||||
|
height: 50%;
|
||||||
|
margin-top: 22vh;
|
||||||
position: static;
|
position: static;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
@ -62,6 +64,10 @@ app-gallery-lightbox-photo {
|
|||||||
opacity: 0.1;
|
opacity: 0.1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#swipeable-container{
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
#rightArrow {
|
#rightArrow {
|
||||||
float: right;
|
float: right;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
@ -94,11 +100,43 @@ app-gallery-lightbox-photo {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.controls-playback {
|
.controls-playback {
|
||||||
padding-right: 15px;
|
padding-right: 15px;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
|
.controls-video {
|
||||||
|
padding-right: 15px;
|
||||||
|
bottom: 0;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls-video .oi,
|
||||||
|
.controls-video input{
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls-big-play span{
|
||||||
|
font-size: 20vh;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls-video input[type=range]{
|
||||||
|
padding: 0;
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls-video .oi{
|
||||||
|
text-align: center;
|
||||||
|
max-width: 45px;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.highlight {
|
.highlight {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
|
|||||||
@ -12,73 +12,98 @@
|
|||||||
</app-gallery-lightbox-photo>
|
</app-gallery-lightbox-photo>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
(swipeleft)="nextImage()" (swiperight)="prevImage()" (swipeup)="hide()"
|
|
||||||
*ngIf="controllersVisible"
|
*ngIf="controllersVisible"
|
||||||
id="controllers-container" #controls [style.width.px]="getPhotoFrameWidth()"
|
id="controllers-container"
|
||||||
|
#controls
|
||||||
|
[style.width.px]="getPhotoFrameWidth()"
|
||||||
[ngClass]="!controllersDimmed ? 'dim-controls': ''">
|
[ngClass]="!controllersDimmed ? 'dim-controls': ''">
|
||||||
<div class="controls controls-top">
|
<div id="swipeable-container"
|
||||||
<a *ngIf="activePhoto"
|
(swipeleft)="nextImage()"
|
||||||
class="highlight control-button"
|
(swiperight)="prevImage()"
|
||||||
[href]="activePhoto.gridPhoto.getPhotoPath()"
|
(swipeup)="hide()"
|
||||||
[download]="activePhoto.gridPhoto.media.name">
|
(click)="photo.playPause()">
|
||||||
|
|
||||||
|
<div class="controls controls-top">
|
||||||
|
<a *ngIf="activePhoto"
|
||||||
|
class="highlight control-button"
|
||||||
|
[href]="activePhoto.gridPhoto.getPhotoPath()"
|
||||||
|
[download]="activePhoto.gridPhoto.media.name">
|
||||||
<span class="oi oi-data-transfer-download"
|
<span class="oi oi-data-transfer-download"
|
||||||
title="download" i18n-title></span>
|
title="download" i18n-title></span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class=" highlight control-button" (click)="toggleInfoPanel()"
|
<div class=" highlight control-button" (click)="toggleInfoPanel()"
|
||||||
title="info" i18n-title>
|
title="info" i18n-title>
|
||||||
<span class="oi oi-info"></span>
|
<span class="oi oi-info"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="fullScreenService.isFullScreenEnabled()"
|
<div *ngIf="fullScreenService.isFullScreenEnabled()"
|
||||||
class=" highlight control-button"
|
class=" highlight control-button"
|
||||||
(click)="fullScreenService.exitFullScreen()"
|
(click)="fullScreenService.exitFullScreen()"
|
||||||
title="toggle fullscreen" i18n-title>
|
title="toggle fullscreen" i18n-title>
|
||||||
<span class="oi oi-fullscreen-exit">
|
<span class="oi oi-fullscreen-exit">
|
||||||
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="!fullScreenService.isFullScreenEnabled()"
|
<div *ngIf="!fullScreenService.isFullScreenEnabled()"
|
||||||
class="highlight control-button"
|
class="highlight control-button"
|
||||||
(click)="fullScreenService.showFullScreen(root)"
|
(click)="fullScreenService.showFullScreen(root)"
|
||||||
title="toggle fullscreen" i18n-title>
|
title="toggle fullscreen" i18n-title>
|
||||||
<span class="oi oi-fullscreen-enter">
|
<span class="oi oi-fullscreen-enter">
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="highlight control-button"
|
<div class="highlight control-button"
|
||||||
(click)="hide()"
|
(click)="hide()"
|
||||||
title="close" i18n-title>
|
title="close" i18n-title>
|
||||||
<span class="oi oi-x">
|
<span class="oi oi-x">
|
||||||
|
|
||||||
</span>
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="navigation-arrow highlight"
|
<div class="navigation-arrow highlight"
|
||||||
*ngIf="navigation.hasPrev" title="key: left arrow" id="leftArrow" i18n-title
|
*ngIf="navigation.hasPrev" title="key: left arrow" id="leftArrow" i18n-title
|
||||||
(click)="prevImage()"><span
|
(click)="prevImage()"><span
|
||||||
class="oi oi-chevron-left"></span></div>
|
class="oi oi-chevron-left"></span></div>
|
||||||
<div class="navigation-arrow highlight"
|
<div class="navigation-arrow highlight"
|
||||||
*ngIf="navigation.hasNext" title="key: right arrow" id="rightArrow" i18n-title
|
*ngIf="navigation.hasNext" title="key: right arrow" id="rightArrow" i18n-title
|
||||||
(click)="nextImage()"><span
|
(click)="nextImage()"><span
|
||||||
class="oi oi-chevron-right"></span></div>
|
class="oi oi-chevron-right"></span></div>
|
||||||
|
|
||||||
<div class="controls controls-playback">
|
<div class="controls controls-playback" *ngIf="activePhoto && activePhoto.gridPhoto.isPhoto()">
|
||||||
<span class="oi oi-media-pause highlight control-button"
|
<span class="oi oi-media-pause highlight control-button"
|
||||||
[ngClass]="playBackState == 0 ? 'button-disabled':''"
|
[ngClass]="playBackState == 0 ? 'button-disabled':''"
|
||||||
(click)="pause()"
|
(click)="pause()"
|
||||||
title="pause"></span>
|
title="pause"></span>
|
||||||
<span
|
<span
|
||||||
class="oi oi-media-play highlight control-button"
|
class="oi oi-media-play highlight control-button"
|
||||||
[ngClass]="playBackState == 1 ? 'button-active':''"
|
[ngClass]="playBackState == 1 ? 'button-active':''"
|
||||||
(click)="play()"
|
(click)="play()"
|
||||||
title="auto play"></span>
|
title="auto play"></span>
|
||||||
<span class="oi oi-media-skip-forward highlight control-button"
|
<span class="oi oi-media-skip-forward highlight control-button"
|
||||||
[ngClass]="playBackState == 2 ? 'button-active':''"
|
[ngClass]="playBackState == 2 ? 'button-active':''"
|
||||||
(click)="fastForward()"
|
(click)="fastForward()"
|
||||||
title="fast auto play"></span>
|
title="fast auto play"></span>
|
||||||
|
</div>
|
||||||
|
<div class="controls controls-big-play" *ngIf=" activePhoto && activePhoto.gridPhoto.isVideo() && photo.Paused">
|
||||||
|
<span class="oi oi-media-play"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="controls controls-video row" *ngIf="activePhoto && activePhoto.gridPhoto.isVideo()">
|
||||||
|
<span class="oi col-1"
|
||||||
|
[ngClass]="!photo.Paused ? 'oi-media-pause':'oi-media-play'"
|
||||||
|
(click)="photo.playPause()"></span>
|
||||||
|
<input type="range" [(ngModel)]="photo.VideoProgress"
|
||||||
|
min="0" max="100" step="0.1" class="col video-progress">
|
||||||
|
<span class="oi col-1"
|
||||||
|
[ngClass]="photo.Muted ? 'oi-volume-off':'oi-volume-high'"
|
||||||
|
(click)="photo.mute()"></span>
|
||||||
|
<input type="range"
|
||||||
|
[(ngModel)]="photo.VideoVolume" min="0" max="1" step="0.1"
|
||||||
|
value="1" class="col-2 col-md-1 volume">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<app-info-panel *ngIf="activePhoto && infoPanelVisible"
|
<app-info-panel *ngIf="activePhoto && infoPanelVisible"
|
||||||
|
|||||||
@ -32,7 +32,7 @@ export enum LightboxStates {
|
|||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-gallery-lightbox',
|
selector: 'app-gallery-lightbox',
|
||||||
styleUrls: ['./lightbox.gallery.component.css'],
|
styleUrls: ['./lightbox.gallery.component.css', './inputrange.css'],
|
||||||
templateUrl: './lightbox.gallery.component.html'
|
templateUrl: './lightbox.gallery.component.html'
|
||||||
})
|
})
|
||||||
export class GalleryLightboxComponent implements OnDestroy, OnInit {
|
export class GalleryLightboxComponent implements OnDestroy, OnInit {
|
||||||
@ -243,6 +243,11 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
|
|||||||
case 'Escape': // escape
|
case 'Escape': // escape
|
||||||
this.hide();
|
this.hide();
|
||||||
break;
|
break;
|
||||||
|
case ' ': // space
|
||||||
|
if (this.activePhoto && this.activePhoto.gridPhoto.isVideo()) {
|
||||||
|
this.photoElement.playPause();
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -354,6 +359,11 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
|
|||||||
if (this.photoElement.imageLoadFinished === false) {
|
if (this.photoElement.imageLoadFinished === false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// do not skip video if its playing
|
||||||
|
if (this.activePhoto && this.activePhoto.gridPhoto.isVideo() &&
|
||||||
|
!this.photoElement.Paused) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (this.navigation.hasNext) {
|
if (this.navigation.hasNext) {
|
||||||
this.nextImage();
|
this.nextImage();
|
||||||
} else {
|
} else {
|
||||||
@ -393,6 +403,10 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
|
|||||||
if (this.photoElement.imageLoadFinished === false) {
|
if (this.photoElement.imageLoadFinished === false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (this.activePhoto && this.activePhoto.gridPhoto.isVideo() &&
|
||||||
|
!this.photoElement.Paused) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (this.navigation.hasNext) {
|
if (this.navigation.hasNext) {
|
||||||
this.nextImage();
|
this.nextImage();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -11,16 +11,14 @@
|
|||||||
(load)="onImageLoad()"
|
(load)="onImageLoad()"
|
||||||
(error)="onImageError()"/>
|
(error)="onImageError()"/>
|
||||||
|
|
||||||
<video *ngIf="gridMedia !== null && gridMedia.isVideo() && loadMedia"
|
<video #video
|
||||||
|
*ngIf="gridMedia !== null && gridMedia.isVideo() && loadMedia"
|
||||||
[style.width.%]="imageSize.width"
|
[style.width.%]="imageSize.width"
|
||||||
[style.height.%]="imageSize.height"
|
[style.height.%]="imageSize.height"
|
||||||
(loadeddata)="logevent($event)"
|
(loadstart)="onImageLoad()"
|
||||||
(loadedmetadata)="logevent($event)"
|
|
||||||
(durationchange)="logevent($event)"
|
|
||||||
(loadstart)="logevent($event)"
|
|
||||||
autoplay
|
autoplay
|
||||||
controls
|
(error)="onImageError()"
|
||||||
(error)="onImageError()">
|
(timeupdate)="onVideoProgress()" >
|
||||||
<source [src]="gridMedia.getPhotoPath()" type="video/mp4">
|
<source [src]="gridMedia.getPhotoPath()" type="video/mp4">
|
||||||
</video>
|
</video>
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import {Component, ElementRef, Input, OnChanges} from '@angular/core';
|
import {Component, ElementRef, Input, Output, OnChanges, ViewChild} from '@angular/core';
|
||||||
import {GridMedia} from '../../grid/GridMedia';
|
import {GridMedia} from '../../grid/GridMedia';
|
||||||
import {PhotoDTO} from '../../../../../common/entities/PhotoDTO';
|
import {PhotoDTO} from '../../../../../common/entities/PhotoDTO';
|
||||||
import {FixOrientationPipe} from '../../FixOrientationPipe';
|
import {FixOrientationPipe} from '../../FixOrientationPipe';
|
||||||
@ -14,6 +14,9 @@ export class GalleryLightboxPhotoComponent implements OnChanges {
|
|||||||
@Input() gridMedia: GridMedia;
|
@Input() gridMedia: GridMedia;
|
||||||
@Input() loadMedia = false;
|
@Input() loadMedia = false;
|
||||||
@Input() windowAspect = 1;
|
@Input() windowAspect = 1;
|
||||||
|
|
||||||
|
@ViewChild('video') video: ElementRef<HTMLVideoElement>;
|
||||||
|
|
||||||
prevGirdPhoto = null;
|
prevGirdPhoto = null;
|
||||||
|
|
||||||
public imageSize = {width: 'auto', height: '100'};
|
public imageSize = {width: 'auto', height: '100'};
|
||||||
@ -23,6 +26,7 @@ export class GalleryLightboxPhotoComponent implements OnChanges {
|
|||||||
|
|
||||||
thumbnailSrc: string = null;
|
thumbnailSrc: string = null;
|
||||||
photoSrc: string = null;
|
photoSrc: string = null;
|
||||||
|
private videoProgress: number = 0;
|
||||||
|
|
||||||
constructor(public elementRef: ElementRef) {
|
constructor(public elementRef: ElementRef) {
|
||||||
}
|
}
|
||||||
@ -45,6 +49,76 @@ export class GalleryLightboxPhotoComponent implements OnChanges {
|
|||||||
FixOrientationPipe.transform(this.gridMedia.getPhotoPath(), this.gridMedia.Orientation)
|
FixOrientationPipe.transform(this.gridMedia.getPhotoPath(), this.gridMedia.Orientation)
|
||||||
.then((src) => this.photoSrc = src);
|
.then((src) => this.photoSrc = src);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private onVideoProgress() {
|
||||||
|
this.videoProgress = (100 / this.video.nativeElement.duration) * this.video.nativeElement.currentTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get VideoProgress(): number {
|
||||||
|
return this.videoProgress;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public get VideoVolume(): number {
|
||||||
|
if (!this.video) {
|
||||||
|
return 100;
|
||||||
|
}
|
||||||
|
return this.video.nativeElement.volume;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set VideoVolume(value: number) {
|
||||||
|
if (!this.video) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.video.nativeElement.muted = false;
|
||||||
|
this.video.nativeElement.volume = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set VideoProgress(value: number) {
|
||||||
|
if (!this.video && value === null && typeof value === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.video.nativeElement.currentTime = this.video.nativeElement.duration * (value / 100);
|
||||||
|
if (this.video.nativeElement.paused) {
|
||||||
|
this.video.nativeElement.play().catch(console.error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public get Muted(): boolean {
|
||||||
|
if (!this.video) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return this.video.nativeElement.muted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public mute() {
|
||||||
|
if (!this.video) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.video.nativeElement.muted = !this.video.nativeElement.muted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public playPause() {
|
||||||
|
if (!this.video) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.video.nativeElement.paused) {
|
||||||
|
this.video.nativeElement.play().catch(console.error);
|
||||||
|
} else {
|
||||||
|
this.video.nativeElement.pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get Paused(): boolean {
|
||||||
|
if (!this.video) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return this.video.nativeElement.paused;
|
||||||
}
|
}
|
||||||
|
|
||||||
onImageError() {
|
onImageError() {
|
||||||
@ -53,11 +127,6 @@ export class GalleryLightboxPhotoComponent implements OnChanges {
|
|||||||
console.error('Error: cannot load image for lightbox url: ' + this.gridMedia.getPhotoPath());
|
console.error('Error: cannot load image for lightbox url: ' + this.gridMedia.getPhotoPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
logevent(ev) {
|
|
||||||
console.log(ev);
|
|
||||||
this.imageLoadFinished = true;
|
|
||||||
this.imageLoaded = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
onImageLoad() {
|
onImageLoad() {
|
||||||
this.imageLoadFinished = true;
|
this.imageLoadFinished = true;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user