implementing video controls

This commit is contained in:
Patrik J. Braun 2018-11-11 19:22:46 +01:00
parent f13f333d49
commit 6ca35f2f20
6 changed files with 272 additions and 60 deletions

View 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;
}

View File

@ -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;

View File

@ -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"

View File

@ -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 {

View File

@ -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>

View File

@ -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;