Skip to content

Commit

Permalink
viewer: upstream video logic
Browse files Browse the repository at this point in the history
  • Loading branch information
pulsejet committed Nov 8, 2022
1 parent d58be31 commit 067624d
Show file tree
Hide file tree
Showing 2 changed files with 242 additions and 67 deletions.
231 changes: 231 additions & 0 deletions src/components/PsVideo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
import PhotoSwipe from "photoswipe";

/**
* Check if slide has video content
*
* @param {Slide|Content} content Slide or Content object
* @returns Boolean
*/
function isVideoContent(content): boolean {
return content?.data?.type === "video";
}

class VideoContentSetup {
constructor(lightbox: PhotoSwipe, private options) {
this.initLightboxEvents(lightbox);
lightbox.on("init", () => {
this.initPswpEvents(lightbox);
});
}

initLightboxEvents(lightbox: PhotoSwipe) {
lightbox.on("contentLoad", this.onContentLoad.bind(this));
lightbox.on("contentDestroy", this.onContentDestroy.bind(this));
lightbox.on("contentActivate", this.onContentActivate.bind(this));
lightbox.on("contentDeactivate", this.onContentDeactivate.bind(this));
lightbox.on("contentAppend", this.onContentAppend.bind(this));
lightbox.on("contentResize", this.onContentResize.bind(this));

lightbox.addFilter(
"isKeepingPlaceholder",
this.isKeepingPlaceholder.bind(this)
);
lightbox.addFilter("isContentZoomable", this.isContentZoomable.bind(this));
lightbox.addFilter(
"useContentPlaceholder",
this.useContentPlaceholder.bind(this)
);

lightbox.addFilter("domItemData", (itemData, element, linkEl) => {
return itemData;
});
}

initPswpEvents(pswp: PhotoSwipe) {
// Prevent draggin when pointer is in bottom part of the video
// todo: add option for this
pswp.on("pointerDown", (e) => {
const slide = pswp.currSlide;
if (isVideoContent(slide) && this.options.preventDragOffset) {
const origEvent = e.originalEvent;
if (origEvent.type === "pointerdown") {
const videoHeight = Math.ceil(slide.height * slide.currZoomLevel);
const verticalEnding = videoHeight + slide.bounds.center.y;
const pointerYPos = origEvent.pageY - pswp.offset.y;
if (
pointerYPos > verticalEnding - this.options.preventDragOffset &&
pointerYPos < verticalEnding
) {
e.preventDefault();
}
}
}
});

// do not append video on nearby slides
pswp.on("appendHeavy", (e) => {
if (isVideoContent(e.slide) && !e.slide.isActive) {
e.preventDefault();
}
});

pswp.on("close", () => {
if (isVideoContent(pswp.currSlide.content)) {
// Switch from zoom to fade closing transition,
// as zoom transition is choppy for videos
if (
!pswp.options.showHideAnimationType ||
pswp.options.showHideAnimationType === "zoom"
) {
pswp.options.showHideAnimationType = "fade";
}

// pause video when closing
this.pauseVideo(pswp.currSlide.content);
}
});
}

onContentDestroy({ content }) {
if (isVideoContent(content)) {
if (content._videoPosterImg) {
content._videoPosterImg.onload = content._videoPosterImg.onerror = null;
content._videoPosterImg = null;
}
}
}

onContentResize(e) {
if (isVideoContent(e.content)) {
e.preventDefault();

const width = e.width;
const height = e.height;
const content = e.content;

if (content.element) {
content.element.style.width = width + "px";
content.element.style.height = height + "px";
}

if (content.slide && content.slide.placeholder) {
// override placeholder size, so it more accurately matches the video
const placeholderElStyle = content.slide.placeholder.element.style;
placeholderElStyle.transform = "none";
placeholderElStyle.width = width + "px";
placeholderElStyle.height = height + "px";
}
}
}

isKeepingPlaceholder(isZoomable, content) {
if (isVideoContent(content)) {
return false;
}
return isZoomable;
}

isContentZoomable(isZoomable, content) {
if (isVideoContent(content)) {
return false;
}
return isZoomable;
}

onContentActivate({ content }) {
if (isVideoContent(content) && this.options.autoplay) {
this.playVideo(content);
}
}

onContentDeactivate({ content }) {
if (isVideoContent(content)) {
this.pauseVideo(content);
}
}

onContentAppend(e) {
if (isVideoContent(e.content)) {
e.preventDefault();
e.content.isAttached = true;
e.content.appendImage();
}
}

onContentLoad(e) {
const content = e.content; // todo: videocontent

if (!isVideoContent(e.content)) {
return;
}

// stop default content load
e.preventDefault();

if (content.element) {
return;
}

content.state = "loading";
content.type = "video"; // TODO: move this to pswp core?

content.element = document.createElement("video");

if (this.options.videoAttributes) {
for (let key in this.options.videoAttributes) {
content.element.setAttribute(
key,
this.options.videoAttributes[key] || ""
);
}
}

content.element.setAttribute("poster", content.data.msrc);

this.preloadVideoPoster(content, content.data.msrc);

content.element.style.position = "absolute";
content.element.style.left = 0;
content.element.style.top = 0;

if (content.data.src) {
content.element.src = content.data.src;
}
}

preloadVideoPoster(content, src) {
if (!content._videoPosterImg && src) {
content._videoPosterImg = new Image();
content._videoPosterImg.src = src;
if (content._videoPosterImg.complete) {
content.onLoaded();
} else {
content._videoPosterImg.onload = content._videoPosterImg.onerror =
() => {
content.onLoaded();
};
}
}
}

playVideo(content) {
if (content.element) {
content.element.play();
}
}

pauseVideo(content) {
if (content.element) {
content.element.pause();
}
}

useContentPlaceholder(usePlaceholder, content) {
if (isVideoContent(content)) {
return true;
}
return usePlaceholder;
}
}

export default VideoContentSetup;
78 changes: 11 additions & 67 deletions src/components/Viewer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,7 @@ import { getDownloadLink } from "../services/DavRequests";
import PhotoSwipe, { PhotoSwipeOptions } from "photoswipe";
import "photoswipe/style.css";
import videojs from "video.js";
import "video.js/dist/video-js.css";
import PsVideo from "./PsVideo";
import ShareIcon from "vue-material-design-icons/ShareVariant.vue";
import DeleteIcon from "vue-material-design-icons/Delete.vue";
Expand Down Expand Up @@ -357,63 +356,10 @@ export default class Viewer extends Mixins(GlobalMixin) {
});
// Video support
this.photoswipe.on("contentLoad", (e) => {
const { content, isLazy } = e;
if ((content.data?.photo?.flag || 0) & this.c.FLAG_IS_VIDEO) {
e.preventDefault();
content.type = "video";
// Create video element
content.videoElement = document.createElement("video") as any;
content.videoElement.setAttribute("preload", "none");
content.videoElement.classList.add("video-js");
// Get DAV URL for video
const url = getDownloadLink(content.data.photo);
// Add child with source element
const source = document.createElement("source");
source.src = generateUrl(url);
source.type = content.data.photo.mimetype;
content.videoElement.appendChild(source);
// Create container div
content.element = document.createElement("div");
content.element.appendChild(content.videoElement);
// Init videojs
videojs(content.videoElement, {
fluid: true,
autoplay: content.data.playvideo,
controls: true,
preload: "none",
muted: true,
html5: {
vhs: {
withCredentials: true,
},
},
});
}
});
// Play video on open slide
this.photoswipe.on("slideActivate", (e) => {
const { slide } = e;
if ((slide.data?.photo?.flag || 0) & this.c.FLAG_IS_VIDEO) {
setTimeout(() => {
slide.content.element.querySelector("video")?.play();
}, 500);
}
});
// Pause video on close slide
this.photoswipe.on("slideDeactivate", (e) => {
const { slide } = e;
if ((slide.data?.photo?.flag || 0) & this.c.FLAG_IS_VIDEO) {
slide.content.element.querySelector("video")?.pause();
}
new PsVideo(this.photoswipe, {
videoAttributes: { controls: "", playsinline: "", preload: "none" },
autoplay: true,
preventDragOffset: 40,
});
return this.photoswipe;
Expand Down Expand Up @@ -511,10 +457,8 @@ export default class Viewer extends Mixins(GlobalMixin) {
// Get thumb image
const thumbSrc: string =
photo.flag & this.c.FLAG_IS_VIDEO
? undefined
: this.thumbElem(photo)?.getAttribute("src") ||
getPreviewUrl(photo, false, 256);
this.thumbElem(photo)?.getAttribute("src") ||
getPreviewUrl(photo, false, 256);
// Get full image
return {
Expand Down Expand Up @@ -573,10 +517,13 @@ export default class Viewer extends Mixins(GlobalMixin) {
/** Get base data object */
private getItemData(photo: IPhoto) {
let previewUrl = getPreviewUrl(photo, false, 1024);
const isvideo = photo.flag & this.c.FLAG_IS_VIDEO;
// Preview aren't animated
if (photo.mimetype === "image/gif") {
previewUrl = getDownloadLink(photo);
} else if (isvideo) {
previewUrl = generateUrl(getDownloadLink(photo));
}
return {
Expand All @@ -585,6 +532,7 @@ export default class Viewer extends Mixins(GlobalMixin) {
height: photo.h || undefined,
thumbCropped: true,
photo: photo,
type: isvideo ? "video" : "image",
};
}
Expand Down Expand Up @@ -883,10 +831,6 @@ export default class Viewer extends Mixins(GlobalMixin) {
width: inherit;
}
:deep .video-js .vjs-big-play-button {
display: none;
}
:deep .pswp {
.pswp__zoom-wrap {
width: 100%;
Expand Down

0 comments on commit 067624d

Please sign in to comment.