livephoto: add viewer playback
parent
48fabeb445
commit
ff634c09cf
|
@ -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'],
|
||||
|
|
|
@ -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;
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue