livephoto: add viewer playback

pull/231/head
Varun Patil 2022-11-22 05:04:15 -08:00
parent 48fabeb445
commit ff634c09cf
4 changed files with 121 additions and 0 deletions

View File

@ -59,6 +59,7 @@ return [
['name' => 'Archive#archive', 'url' => '/api/archive/{id}', 'verb' => 'PATCH'],
['name' => 'Video#transcode', 'url' => '/api/video/transcode/{client}/{fileid}/{profile}', 'verb' => 'GET'],
['name' => 'Video#livephoto', 'url' => '/api/video/livephoto/{fileid}', 'verb' => 'GET'],
// Config API
['name' => 'Other#setUserConfig', 'url' => '/api/config/{key}', 'verb' => 'PUT'],

View File

@ -0,0 +1,85 @@
import PhotoSwipe from "photoswipe";
import { generateUrl } from "@nextcloud/router";
function isLiveContent(content): boolean {
return Boolean(content?.data?.photo?.liveid);
}
class LivePhotoContentSetup {
constructor(lightbox: PhotoSwipe, private options) {
this.initLightboxEvents(lightbox);
}
initLightboxEvents(lightbox: PhotoSwipe) {
lightbox.on("contentLoad", this.onContentLoad.bind(this));
lightbox.on("contentActivate", this.onContentActivate.bind(this));
lightbox.on("contentDeactivate", this.onContentDeactivate.bind(this));
lightbox.on("contentAppend", this.onContentAppend.bind(this));
}
onContentLoad(e) {
const content = e.content;
if (!isLiveContent(content)) return;
e.preventDefault();
if (content.element) return;
const photo = content?.data?.photo;
const video = document.createElement("video");
video.muted = true;
video.autoplay = false;
video.preload = "none";
video.src = generateUrl(
`/apps/memories/api/video/livephoto/${photo.fileid}?etag=${photo.etag}&liveid=${photo.liveid}`
);
const div = document.createElement("div");
div.className = "livephoto";
div.appendChild(video);
content.element = div;
video.onplay = () => {
div.classList.add("playing");
};
video.oncanplay = () => {
div.classList.add("canplay");
};
video.onended = video.onpause = () => {
div.classList.remove("playing");
};
const img = document.createElement("img");
img.src = content.data.src;
img.onload = () => content.onLoaded();
div.appendChild(img);
content.element = div;
}
onContentActivate({ content }) {
if (isLiveContent(content) && content.element) {
content.element.querySelector("video")?.play();
}
}
onContentDeactivate({ content }) {
if (isLiveContent(content) && content.element) {
const vid = content.element.querySelector("video");
if (vid) {
vid.pause();
vid.currentTime = 0;
}
}
}
onContentAppend(e) {
if (isLiveContent(e.content)) {
e.preventDefault();
e.content.isAttached = true;
e.content.appendImage();
}
}
}
export default LivePhotoContentSetup;

View File

@ -125,6 +125,7 @@ import PhotoSwipe, { PhotoSwipeOptions } from "photoswipe";
import "photoswipe/style.css";
import PsVideo from "./PsVideo";
import PsLivePhoto from "./PsLivePhoto";
import ShareIcon from "vue-material-design-icons/ShareVariant.vue";
import DeleteIcon from "vue-material-design-icons/TrashCanOutline.vue";
@ -418,6 +419,9 @@ export default class Viewer extends Mixins(GlobalMixin) {
preventDragOffset: 40,
});
// Live photo support
new PsLivePhoto(this.photoswipe, {});
return this.photoswipe;
}
@ -900,6 +904,35 @@ export default class Viewer extends Mixins(GlobalMixin) {
.pswp__top-bar {
background: linear-gradient(0deg, transparent, rgba(0, 0, 0, 0.3));
}
.livephoto {
position: relative;
overflow: hidden;
contain: strict;
img,
video {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: block;
transition: opacity 0.5s ease-in-out, transform 0.4s ease-in-out;
}
video,
&.playing.canplay img {
opacity: 0;
}
img,
&.playing.canplay video {
opacity: 1;
}
&.playing.canplay img {
transform: scale(1.07);
}
}
}
:deep .video-js .vjs-big-play-button {

View File

@ -57,6 +57,8 @@ export type IPhoto = {
w?: number;
/** Height of full image */
h?: number;
/** Live photo identifier */
liveid?: string;
/** Grid display width px */
dispW?: number;