viewer: animate lp icon

Signed-off-by: Varun Patil <radialapps@gmail.com>
pull/900/head
Varun Patil 2023-10-30 02:33:46 -07:00
parent c6f5ed5b05
commit 5ccba14519
4 changed files with 48 additions and 27 deletions

View File

@ -24,7 +24,7 @@
<VideoIcon :size="22" /> <VideoIcon :size="22" />
</div> </div>
<div class="livephoto" v-if="data.liveid" @mouseenter.passive="playVideo" @mouseleave.passive="stopVideo"> <div class="livephoto" v-if="data.liveid" @mouseenter.passive="playVideo" @mouseleave.passive="stopVideo">
<LivePhotoIcon :size="22" :spin="liveWaiting" :playing="livePlaying" /> <LivePhotoIcon :size="22" :spin="liveState.waiting" :playing="liveState.playing" />
</div> </div>
<RawIcon class="raw" v-if="isRaw" :size="28" /> <RawIcon class="raw" v-if="isRaw" :size="28" />
</div> </div>
@ -119,9 +119,11 @@ export default defineComponent({
data: () => ({ data: () => ({
touchTimer: 0, touchTimer: 0,
livePlayTimer: 0, liveState: {
liveWaiting: false, playTimer: 0,
livePlaying: false, playing: false,
waiting: false,
},
faceSrc: null as string | null, faceSrc: null as string | null,
}), }),
@ -140,17 +142,14 @@ export default defineComponent({
// Setup video hooks // Setup video hooks
const video = this.refs.video; const video = this.refs.video;
if (video) { if (video) {
utils.setupLivePhotoHooks(video); utils.setupLivePhotoHooks(video, this.liveState);
video.addEventListener('playing', () => (this.livePlaying = true));
video.addEventListener('pause', () => (this.livePlaying = false));
video.addEventListener('ended', () => (this.livePlaying = false));
} }
}, },
/** Clear timers */ /** Clear timers */
beforeDestroy() { beforeDestroy() {
clearTimeout(this.touchTimer); clearTimeout(this.touchTimer);
clearTimeout(this.livePlayTimer); clearTimeout(this.liveState.playTimer);
// Clean up blob url if face rect was created // Clean up blob url if face rect was created
if (this.faceSrc) { if (this.faceSrc) {
@ -277,11 +276,11 @@ export default defineComponent({
/** Start preview video */ /** Start preview video */
playVideo() { playVideo() {
this.liveWaiting = true; this.liveState.waiting = true;
utils.setRenewingTimeout( utils.setRenewingTimeout(
this, this.liveState,
'livePlayTimer', 'playTimer',
async () => { async () => {
const video = this.refs.video; const video = this.refs.video;
if (!video || this.data.flag & this.c.FLAG_SELECTED) return; if (!video || this.data.flag & this.c.FLAG_SELECTED) return;
@ -292,7 +291,7 @@ export default defineComponent({
} catch (e) { } catch (e) {
// ignore, pause was probably called too soon // ignore, pause was probably called too soon
} finally { } finally {
this.liveWaiting = false; this.liveState.waiting = false;
} }
}, },
400, 400,
@ -302,10 +301,9 @@ export default defineComponent({
/** Stop preview video */ /** Stop preview video */
stopVideo() { stopVideo() {
this.refs.video?.pause(); this.refs.video?.pause();
window.clearTimeout(this.livePlayTimer); window.clearTimeout(this.liveState.playTimer);
this.livePlayTimer = 0; this.liveState.playTimer = 0;
this.liveWaiting = false; this.liveState.waiting = false;
this.livePlaying = false;
}, },
}, },
}); });

View File

@ -20,6 +20,7 @@ class LivePhotoContentSetup {
constructor( constructor(
lightbox: PhotoSwipe, lightbox: PhotoSwipe,
private psImage: PsImage, private psImage: PsImage,
private liveState: { playing: boolean; waiting: boolean },
) { ) {
lightbox.on('contentLoad', this.onContentLoad.bind(this)); lightbox.on('contentLoad', this.onContentLoad.bind(this));
lightbox.on('contentActivate', this.onContentActivate.bind(this)); lightbox.on('contentActivate', this.onContentActivate.bind(this));
@ -32,10 +33,13 @@ class LivePhotoContentSetup {
if (!video) return; if (!video) return;
try { try {
this.liveState.waiting = true;
video.currentTime = 0; video.currentTime = 0;
await video.play(); await video.play();
} catch (e) { } catch (e) {
// ignore, pause was probably called too soon // ignore, pause was probably called too soon
} finally {
this.liveState.waiting = false;
} }
} }
@ -60,7 +64,7 @@ class LivePhotoContentSetup {
div.appendChild(video); div.appendChild(video);
content.element = div; content.element = div;
utils.setupLivePhotoHooks(video); utils.setupLivePhotoHooks(video, this.liveState);
const img = this.psImage.getXImgElem(content, () => content.onLoaded()); const img = this.psImage.getXImgElem(content, () => content.onLoaded());
div.appendChild(img); div.appendChild(img);

View File

@ -55,7 +55,9 @@
:close-after-click="true" :close-after-click="true"
> >
{{ t('memories', 'Play Live Photo') }} {{ t('memories', 'Play Live Photo') }}
<template #icon> <LivePhotoIcon :size="24" /> </template> <template #icon>
<LivePhotoIcon :size="24" :playing="liveState.playing" :spin="liveState.waiting" />
</template>
</NcActionButton> </NcActionButton>
<NcActionButton <NcActionButton
v-if="!routeIsPublic && !isLocal" v-if="!routeIsPublic && !isLocal"
@ -194,6 +196,7 @@ import PsLivePhoto from './PsLivePhoto';
import type { IImageInfo, IPhoto, TimelineState } from '@typings'; import type { IImageInfo, IPhoto, TimelineState } from '@typings';
import type { PsContent } from './types'; import type { PsContent } from './types';
import LivePhotoIcon from '@components/icons/LivePhoto.vue';
import ShareIcon from 'vue-material-design-icons/ShareVariant.vue'; import ShareIcon from 'vue-material-design-icons/ShareVariant.vue';
import DeleteIcon from 'vue-material-design-icons/TrashCanOutline.vue'; import DeleteIcon from 'vue-material-design-icons/TrashCanOutline.vue';
import StarIcon from 'vue-material-design-icons/Star.vue'; import StarIcon from 'vue-material-design-icons/Star.vue';
@ -205,7 +208,6 @@ import TuneIcon from 'vue-material-design-icons/Tune.vue';
import SlideshowIcon from 'vue-material-design-icons/PlayBox.vue'; import SlideshowIcon from 'vue-material-design-icons/PlayBox.vue';
import EditFileIcon from 'vue-material-design-icons/FileEdit.vue'; import EditFileIcon from 'vue-material-design-icons/FileEdit.vue';
import AlbumRemoveIcon from 'vue-material-design-icons/BookRemove.vue'; import AlbumRemoveIcon from 'vue-material-design-icons/BookRemove.vue';
import LivePhotoIcon from 'vue-material-design-icons/MotionPlayOutline.vue';
import AlbumIcon from 'vue-material-design-icons/ImageAlbum.vue'; import AlbumIcon from 'vue-material-design-icons/ImageAlbum.vue';
const SLIDESHOW_MS = 5000; const SLIDESHOW_MS = 5000;
@ -220,6 +222,7 @@ export default defineComponent({
NcActions, NcActions,
NcActionButton, NcActionButton,
ImageEditor, ImageEditor,
LivePhotoIcon,
ShareIcon, ShareIcon,
DeleteIcon, DeleteIcon,
StarIcon, StarIcon,
@ -231,7 +234,6 @@ export default defineComponent({
SlideshowIcon, SlideshowIcon,
EditFileIcon, EditFileIcon,
AlbumRemoveIcon, AlbumRemoveIcon,
LivePhotoIcon,
AlbumIcon, AlbumIcon,
}, },
@ -260,6 +262,13 @@ export default defineComponent({
psImage: null as PsImage | null, psImage: null as PsImage | null,
psLivePhoto: null as PsLivePhoto | null, psLivePhoto: null as PsLivePhoto | null,
/** Live photo state */
liveState: {
playing: false,
waiting: false,
},
/** List globals */
list: [] as IPhoto[], list: [] as IPhoto[],
globalCount: 0, globalCount: 0,
globalAnchor: -1, globalAnchor: -1,
@ -590,15 +599,15 @@ export default defineComponent({
}); });
// Video support // Video support
this.psVideo = new PsVideo(<PhotoSwipe>this.photoswipe, { this.psVideo = new PsVideo(<any>this.photoswipe, {
preventDragOffset: 40, preventDragOffset: 40,
}); });
// Image support // Image support
this.psImage = new PsImage(<PhotoSwipe>this.photoswipe); this.psImage = new PsImage(<any>this.photoswipe);
// Live Photo support // Live Photo support
this.psLivePhoto = new PsLivePhoto(<PhotoSwipe>this.photoswipe, <PsImage>this.psImage); this.psLivePhoto = new PsLivePhoto(<any>this.photoswipe, <any>this.psImage, this.liveState);
// Patch the close button to stop the slideshow // Patch the close button to stop the slideshow
const _close = this.photoswipe.close.bind(this.photoswipe); const _close = this.photoswipe.close.bind(this.photoswipe);

View File

@ -162,13 +162,23 @@ export function getLivePhotoVideoUrl(p: IPhoto, transcode: boolean) {
/** /**
* Set up hooks to set classes on parent element for Live Photo * Set up hooks to set classes on parent element for Live Photo
* @param video Video element * @param video Video element
* @param parent State object to update (reactivity)
*/ */
export function setupLivePhotoHooks(video: HTMLVideoElement) { export function setupLivePhotoHooks(video: HTMLVideoElement, state: { playing: boolean }) {
const div = video.closest('.memories-livephoto') as HTMLDivElement; const div = video.closest('.memories-livephoto') as HTMLDivElement;
// Playing state
video.addEventListener('playing', () => (state.playing = true));
video.addEventListener('play', () => div.classList.add('playing')); video.addEventListener('play', () => div.classList.add('playing'));
video.addEventListener('canplay', () => div.classList.add('canplay')); video.addEventListener('canplay', () => div.classList.add('canplay'));
video.addEventListener('ended', () => div.classList.remove('playing'));
video.addEventListener('pause', () => div.classList.remove('playing')); // Ended or pausing state
const ended = () => {
state.playing = false;
div.classList.remove('playing');
};
video.addEventListener('ended', ended);
video.addEventListener('pause', ended);
} }
/** /**