1
0
mirror of https://github.com/bpatrik/pigallery2.git synced 2024-11-24 08:42:24 +02:00

Merge pull request #509 from bpatrik/feature/performance

Implementing next photo preloading
This commit is contained in:
Patrik J. Braun 2022-06-26 09:57:50 +02:00 committed by GitHub
commit b837ed167c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 124 additions and 74 deletions

View File

@ -154,6 +154,21 @@
opacity: 1.0;
}
@keyframes blink {
0% {
opacity: 0.5;
}
25% {
opacity: 1.0;
}
75% {
opacity: 1.0;
}
100% {
opacity: 0.5;
}
}
@-webkit-keyframes blink {
0% {
opacity: 0.5;

View File

@ -10,20 +10,20 @@ import {
Output,
ViewChild,
} from '@angular/core';
import { MediaDTOUtils } from '../../../../../../common/entities/MediaDTO';
import { FullScreenService } from '../../fullscreen.service';
import { GalleryPhotoComponent } from '../../grid/photo/photo.grid.gallery.component';
import { Observable, Subscription, timer } from 'rxjs';
import { filter } from 'rxjs/operators';
import { PhotoDTO } from '../../../../../../common/entities/PhotoDTO';
import { GalleryLightboxMediaComponent } from '../media/media.lightbox.gallery.component';
import { Config } from '../../../../../../common/config/public/Config';
import {MediaDTOUtils} from '../../../../../../common/entities/MediaDTO';
import {FullScreenService} from '../../fullscreen.service';
import {GalleryPhotoComponent} from '../../grid/photo/photo.grid.gallery.component';
import {Observable, Subscription, timer} from 'rxjs';
import {filter} from 'rxjs/operators';
import {PhotoDTO} from '../../../../../../common/entities/PhotoDTO';
import {GalleryLightboxMediaComponent} from '../media/media.lightbox.gallery.component';
import {Config} from '../../../../../../common/config/public/Config';
import {
SearchQueryTypes,
TextSearch,
TextSearchQueryMatchTypes,
} from '../../../../../../common/entities/SearchQueryDTO';
import { AuthenticationService } from '../../../../model/network/authentication.service';
import {AuthenticationService} from '../../../../model/network/authentication.service';
export enum PlayBackStates {
Paused = 1,
@ -39,7 +39,7 @@ export enum PlayBackStates {
export class ControlsLightboxComponent implements OnDestroy, OnInit, OnChanges {
readonly MAX_ZOOM = 10;
@ViewChild('root', { static: false }) root: ElementRef;
@ViewChild('root', {static: false}) root: ElementRef;
@Output() closed = new EventEmitter();
@Output() toggleInfoPanel = new EventEmitter();
@ -47,10 +47,10 @@ export class ControlsLightboxComponent implements OnDestroy, OnInit, OnChanges {
@Output() nextPhoto = new EventEmitter();
@Output() previousPhoto = new EventEmitter();
@Input() navigation = { hasPrev: true, hasNext: true };
@Input() navigation = {hasPrev: true, hasNext: true};
@Input() activePhoto: GalleryPhotoComponent;
@Input() mediaElement: GalleryLightboxMediaComponent;
@Input() photoFrameDim = { width: 1, height: 1, aspect: 1 };
@Input() photoFrameDim = {width: 1, height: 1, aspect: 1};
public readonly facesEnabled = Config.Client.Faces.enabled;
@ -60,14 +60,14 @@ export class ControlsLightboxComponent implements OnDestroy, OnInit, OnChanges {
public controllersDimmed = false;
public controllersAlwaysOn = false;
public controllersVisible = true;
public drag = { x: 0, y: 0 };
public drag = {x: 0, y: 0};
public SearchQueryTypes = SearchQueryTypes;
public faceContainerDim = { width: 0, height: 0 };
public faceContainerDim = {width: 0, height: 0};
public searchEnabled: boolean;
private visibilityTimer: number = null;
private timer: Observable<number>;
private timerSub: Subscription;
private prevDrag = { x: 0, y: 0 };
private prevDrag = {x: 0, y: 0};
private prevZoom = 1;
constructor(
@ -257,42 +257,33 @@ export class ControlsLightboxComponent implements OnDestroy, OnInit, OnChanges {
}
}
private showNextMedia = () => {
if (this.mediaElement.imageLoadFinished.this === false ||
this.mediaElement.imageLoadFinished.next === false) {
return;
}
// do not skip video if its playing
if (
this.activePhoto &&
this.activePhoto.gridMedia.isVideo() &&
!this.mediaElement.Paused
) {
return;
}
this.nextPhoto.emit();
};
public play(): void {
this.pause();
this.timerSub = this.timer
.pipe(filter((t) => t % 2 === 0))
.subscribe(() => {
if (this.mediaElement.imageLoadFinished === false) {
return;
}
// do not skip video if its playing
if (
this.activePhoto &&
this.activePhoto.gridMedia.isVideo() &&
!this.mediaElement.Paused
) {
return;
}
this.nextPhoto.emit();
});
.subscribe(this.showNextMedia);
this.playBackState = PlayBackStates.Play;
}
public fastForward(): void {
this.pause();
this.timerSub = this.timer.subscribe(() => {
if (this.mediaElement.imageLoadFinished === false) {
return;
}
if (
this.activePhoto &&
this.activePhoto.gridMedia.isVideo() &&
!this.mediaElement.Paused
) {
return;
}
this.nextPhoto.emit();
});
this.timerSub = this.timer.subscribe(this.showNextMedia);
this.playBackState = PlayBackStates.FastForward;
}

View File

@ -6,6 +6,7 @@
<div class="lightbox" #lightbox>
<app-gallery-lightbox-media [gridMedia]="activePhoto ? activePhoto.gridMedia : null"
[nextGridMedia]="NexGridMedia"
[loadMedia]="!animating"
[zoom]="controls ? controls.Zoom : 1"
[drag]="controls ? controls.drag : {x:0,y:0}"

View File

@ -15,6 +15,7 @@ import {ContentService} from '../content.service';
import {PhotoDTO} from '../../../../../common/entities/PhotoDTO';
import {ControlsLightboxComponent} from './controls/controls.lightbox.gallery.component';
import {SupportedFormats} from '../../../../../common/SupportedFormats';
import {GridMedia} from '../grid/GridMedia';
export enum LightboxStates {
Open = 1,
@ -389,7 +390,7 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
this.router
.navigate([], {
queryParams: this.queryService.getParams(
this.gridPhotoQL.toArray()[photoIndex].gridMedia.media
this.gridPhotoQL.get(photoIndex).gridMedia.media
),
})
.catch(console.error);
@ -440,14 +441,12 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
}
private updateActivePhoto(photoIndex: number, resize = true): void {
const pcList = this.gridPhotoQL.toArray();
if (photoIndex < 0 || photoIndex > this.gridPhotoQL.length) {
throw new Error('Can\'t find the media');
}
this.videoSourceError = false;
this.activePhotoId = photoIndex;
this.activePhoto = pcList[photoIndex];
this.activePhoto = this.gridPhotoQL.get(photoIndex);
if (resize) {
this.animatePhoto(
@ -455,7 +454,7 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
);
}
this.navigation.hasPrev = photoIndex > 0;
this.navigation.hasNext = photoIndex + 1 < pcList.length;
this.navigation.hasNext = photoIndex + 1 < this.gridPhotoQL.length;
const to = this.activePhoto.getDimension();
@ -501,5 +500,12 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
return {top, left, width, height} as Dimension;
}
get NexGridMedia(): GridMedia {
if (this.activePhotoId + 1 < this.gridPhotoQL?.length) {
return this.gridPhotoQL.get(this.activePhotoId + 1)?.gridMedia;
}
return null;
}
}

View File

@ -22,7 +22,7 @@
(error)="onImageError()"
(timeupdate)="onVideoProgress()"
#video>
<source [src]="gridMedia.getBestFitMediaPath()" (error)="onSourceError($event)">
<source [src]="gridMedia.getBestFitMediaPath()" (error)="onSourceError()">
Something went wrong.
</video>

View File

@ -7,11 +7,11 @@ import {
Output,
ViewChild,
} from '@angular/core';
import { GridMedia } from '../../grid/GridMedia';
import { MediaDTOUtils } from '../../../../../../common/entities/MediaDTO';
import { DomSanitizer, SafeStyle } from '@angular/platform-browser';
import { SupportedFormats } from '../../../../../../common/SupportedFormats';
import { Config } from '../../../../../../common/config/public/Config';
import {GridMedia} from '../../grid/GridMedia';
import {MediaDTOUtils} from '../../../../../../common/entities/MediaDTO';
import {DomSanitizer, SafeStyle} from '@angular/platform-browser';
import {SupportedFormats} from '../../../../../../common/SupportedFormats';
import {Config} from '../../../../../../common/config/public/Config';
@Component({
selector: 'app-gallery-lightbox-media',
@ -20,42 +20,50 @@ import { Config } from '../../../../../../common/config/public/Config';
})
export class GalleryLightboxMediaComponent implements OnChanges {
@Input() gridMedia: GridMedia;
@Input() loadMedia = false;
@Input() nextGridMedia: GridMedia;
@Input() loadMedia = false; // prevents loading media
@Input() windowAspect = 1;
@Input() zoom = 1;
@Input() drag = { x: 0, y: 0 };
@Input() drag = {x: 0, y: 0};
@Output() videoSourceError = new EventEmitter();
@ViewChild('video', { static: false }) video: ElementRef<HTMLVideoElement>;
@ViewChild('video', {static: false}) video: ElementRef<HTMLVideoElement>;
prevGirdPhoto: GridMedia = null;
public imageSize = { width: 'auto', height: '100' };
public imageLoadFinished = false;
public imageSize = {width: 'auto', height: '100'};
private nextImage = new Image();
// do not skip to the next photo if not both are loaded (or resulted in an error)
public imageLoadFinished = {
this: false,
next: false
};
thumbnailSrc: string = null;
photo = {
src: null as string,
isBestFit: null as boolean,
};
public transcodeNeedVideos = SupportedFormats.TranscodeNeed.Videos;
// if media not loaded, show thumbnail
private mediaLoaded = false;
private videoProgress = 0;
constructor(public elementRef: ElementRef, private sanitizer: DomSanitizer) {}
constructor(public elementRef: ElementRef, private sanitizer: DomSanitizer) {
}
get ImageTransform(): SafeStyle {
return this.sanitizer.bypassSecurityTrustStyle(
'scale(' +
this.zoom +
') translate(calc(' +
-50 / this.zoom +
'% + ' +
this.drag.x / this.zoom +
'px), calc(' +
-50 / this.zoom +
'% + ' +
this.drag.y / this.zoom +
'px))'
this.zoom +
') translate(calc(' +
-50 / this.zoom +
'% + ' +
this.drag.x / this.zoom +
'px), calc(' +
-50 / this.zoom +
'% + ' +
this.drag.y / this.zoom +
'px))'
);
}
@ -120,8 +128,12 @@ export class GalleryLightboxMediaComponent implements OnChanges {
this.prevGirdPhoto = this.gridMedia;
this.thumbnailSrc = null;
this.photo.src = null;
this.nextImage.src = "";
this.mediaLoaded = false;
this.imageLoadFinished = false;
this.imageLoadFinished = {
this: false,
next: false
};
}
this.setImageSize();
if (
@ -156,16 +168,18 @@ export class GalleryLightboxMediaComponent implements OnChanges {
onImageError(): void {
// TODO:handle error
this.imageLoadFinished = true;
this.imageLoadFinished.this = true;
console.error(
'Error: cannot load media for lightbox url: ' +
this.gridMedia.getBestFitMediaPath()
this.gridMedia.getBestFitMediaPath()
);
this.loadNextPhoto();
}
onImageLoad(): void {
this.imageLoadFinished = true;
this.imageLoadFinished.this = true;
this.mediaLoaded = true;
this.loadNextPhoto();
}
public showThumbnail(): boolean {
@ -178,11 +192,34 @@ export class GalleryLightboxMediaComponent implements OnChanges {
);
}
onSourceError($event: any): void {
onSourceError(): void {
this.mediaLoaded = false;
this.videoSourceError.emit();
}
/**
* Loads next photo to faster show it on navigation.
* Called after the current photo is loaded
* @private
*/
private loadNextPhoto(): void {
if (!this.nextGridMedia || !this.loadMedia) {
return;
}
// Videos do not support preloading
if (!this.nextGridMedia.isPhoto()) {
this.imageLoadFinished.next = true;
return;
}
if (Config.Client.Media.Photo.Converting.enabled === true) {
this.nextImage.src = this.nextGridMedia.getBestFitMediaPath();
} else {
this.nextImage.src = this.nextGridMedia.getMediaPath();
}
this.nextImage.onload = () => this.imageLoadFinished.next = true;
}
private loadPhoto(): void {
if (!this.gridMedia || !this.loadMedia || !this.gridMedia.isPhoto()) {
return;