parent
09ab876ebd
commit
c6478a195d
|
@ -37,6 +37,7 @@
|
|||
"@nextcloud/webpack-vue-config": "^5.4.0",
|
||||
"@playwright/test": "^1.31.2",
|
||||
"@types/url-parse": "^1.4.8",
|
||||
"@types/videojs-contrib-quality-levels": "^2.0.1",
|
||||
"playwright": "^1.31.2",
|
||||
"ts-loader": "^9.4.2",
|
||||
"typescript": "^4.9.5",
|
||||
|
@ -2534,6 +2535,21 @@
|
|||
"integrity": "sha512-zqqcGKyNWgTLFBxmaexGUKQyWqeG7HjXj20EuQJSJWwXe54BjX0ihIo5cJB9yAQzH8dNugJ9GvkBYMjPXs/PJw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/video.js": {
|
||||
"version": "7.3.51",
|
||||
"resolved": "https://registry.npmjs.org/@types/video.js/-/video.js-7.3.51.tgz",
|
||||
"integrity": "sha512-xLlt/ZfCuWYBvG2MRn018RvaEplcK6dI63aOiVUeeAWFyjx3Br1hL749ndFgbrvNdY4m9FoHG1FQ/PB6IpfSAQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/videojs-contrib-quality-levels": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/videojs-contrib-quality-levels/-/videojs-contrib-quality-levels-2.0.1.tgz",
|
||||
"integrity": "sha512-a7vNjolI9zG269Ks8y6JC/Eut+BXex0/1haB8c2J7RCIn365EdTxgiT0udG2ObaCkQqZsa2+4KS7ZE1HMo113w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/video.js": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/ws": {
|
||||
"version": "8.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
|
||||
|
@ -13244,6 +13260,21 @@
|
|||
"integrity": "sha512-zqqcGKyNWgTLFBxmaexGUKQyWqeG7HjXj20EuQJSJWwXe54BjX0ihIo5cJB9yAQzH8dNugJ9GvkBYMjPXs/PJw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/video.js": {
|
||||
"version": "7.3.51",
|
||||
"resolved": "https://registry.npmjs.org/@types/video.js/-/video.js-7.3.51.tgz",
|
||||
"integrity": "sha512-xLlt/ZfCuWYBvG2MRn018RvaEplcK6dI63aOiVUeeAWFyjx3Br1hL749ndFgbrvNdY4m9FoHG1FQ/PB6IpfSAQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/videojs-contrib-quality-levels": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/videojs-contrib-quality-levels/-/videojs-contrib-quality-levels-2.0.1.tgz",
|
||||
"integrity": "sha512-a7vNjolI9zG269Ks8y6JC/Eut+BXex0/1haB8c2J7RCIn365EdTxgiT0udG2ObaCkQqZsa2+4KS7ZE1HMo113w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/video.js": "*"
|
||||
}
|
||||
},
|
||||
"@types/ws": {
|
||||
"version": "8.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
|
||||
|
|
|
@ -64,6 +64,7 @@
|
|||
"@nextcloud/webpack-vue-config": "^5.4.0",
|
||||
"@playwright/test": "^1.31.2",
|
||||
"@types/url-parse": "^1.4.8",
|
||||
"@types/videojs-contrib-quality-levels": "^2.0.1",
|
||||
"playwright": "^1.31.2",
|
||||
"ts-loader": "^9.4.2",
|
||||
"typescript": "^4.9.5",
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import PhotoSwipe from "photoswipe";
|
||||
import Slide from "photoswipe/dist/types/slide/slide";
|
||||
|
||||
import { isVideoContent } from "./PsVideo";
|
||||
import { isLiveContent } from "./PsLivePhoto";
|
||||
import { fetchImage } from "../frame/XImgCache";
|
||||
import { PsContent, PsEvent, PsSlide } from "./types";
|
||||
|
||||
export default class ImageContentSetup {
|
||||
private loading = 0;
|
||||
|
@ -17,11 +17,11 @@ export default class ImageContentSetup {
|
|||
lightbox.addFilter("placeholderSrc", this.placeholderSrc.bind(this));
|
||||
}
|
||||
|
||||
isContentLoading(isLoading: boolean, content: any) {
|
||||
isContentLoading(isLoading: boolean, content: PsContent) {
|
||||
return isLoading || this.loading > 0;
|
||||
}
|
||||
|
||||
onContentLoad(e) {
|
||||
onContentLoad(e: PsEvent) {
|
||||
if (isVideoContent(e.content) || isLiveContent(e.content)) return;
|
||||
|
||||
// Insert image throgh XImgCache
|
||||
|
@ -29,16 +29,16 @@ export default class ImageContentSetup {
|
|||
e.content.element = this.getXImgElem(e.content, () => e.content.onLoaded());
|
||||
}
|
||||
|
||||
onContentLoadImage(e) {
|
||||
onContentLoadImage(e: PsEvent) {
|
||||
if (isVideoContent(e.content) || isLiveContent(e.content)) return;
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
placeholderSrc(placeholderSrc: string, content: any) {
|
||||
placeholderSrc(placeholderSrc: string, content: PsContent) {
|
||||
return content.data.msrc || placeholderSrc;
|
||||
}
|
||||
|
||||
getXImgElem(content: any, onLoad: () => void): HTMLImageElement {
|
||||
getXImgElem(content: PsContent, onLoad: () => void): HTMLImageElement {
|
||||
const img = document.createElement("img");
|
||||
img.classList.add("pswp__img", "ximg");
|
||||
img.style.visibility = "hidden";
|
||||
|
@ -63,7 +63,7 @@ export default class ImageContentSetup {
|
|||
return img;
|
||||
}
|
||||
|
||||
zoomPanUpdate({ slide }: { slide: Slide }) {
|
||||
zoomPanUpdate({ slide }: { slide: PsSlide }) {
|
||||
if (!slide.data.highSrc || slide.data.highSrcCond !== "zoom") return;
|
||||
|
||||
if (slide.currZoomLevel >= slide.zoomLevels.secondary) {
|
||||
|
@ -78,7 +78,7 @@ export default class ImageContentSetup {
|
|||
}
|
||||
}
|
||||
|
||||
loadFullImage(slide: Slide) {
|
||||
loadFullImage(slide: PsSlide) {
|
||||
if (!slide.data.highSrc) return;
|
||||
|
||||
// Get ximg element
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import PhotoSwipe from "photoswipe";
|
||||
import PsImage from "./PsImage";
|
||||
import * as utils from "../../services/Utils";
|
||||
import { PsContent, PsEvent } from "./types";
|
||||
|
||||
export function isLiveContent(content): boolean {
|
||||
export function isLiveContent(content: PsContent): boolean {
|
||||
// Do not play Live Photo if the slideshow is
|
||||
// playing in full screen mode.
|
||||
if (document.fullscreenElement) {
|
||||
|
@ -21,7 +22,7 @@ class LivePhotoContentSetup {
|
|||
}
|
||||
|
||||
onContentLoad(e) {
|
||||
const content = e.content;
|
||||
const content: PsContent = e.content;
|
||||
if (!isLiveContent(content)) return;
|
||||
|
||||
e.preventDefault();
|
||||
|
@ -50,7 +51,7 @@ class LivePhotoContentSetup {
|
|||
content.element = div;
|
||||
}
|
||||
|
||||
onContentActivate({ content }) {
|
||||
onContentActivate({ content }: { content: PsContent }) {
|
||||
if (isLiveContent(content)) {
|
||||
const video = content.element?.querySelector("video");
|
||||
if (video) {
|
||||
|
@ -60,15 +61,15 @@ class LivePhotoContentSetup {
|
|||
}
|
||||
}
|
||||
|
||||
onContentDeactivate({ content }) {
|
||||
onContentDeactivate({ content }: PsEvent) {
|
||||
if (isLiveContent(content)) {
|
||||
content.element?.querySelector("video")?.pause();
|
||||
}
|
||||
}
|
||||
|
||||
onContentDestroy(e) {
|
||||
if (isLiveContent(e.content)) {
|
||||
e.content.element?.remove();
|
||||
onContentDestroy({ content }: PsEvent) {
|
||||
if (isLiveContent(content)) {
|
||||
content.element?.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,27 @@
|
|||
import PhotoSwipe from "photoswipe";
|
||||
import { loadState } from "@nextcloud/initial-state";
|
||||
import axios from "@nextcloud/axios";
|
||||
import { showError } from "@nextcloud/dialogs";
|
||||
import { translate as t } from "@nextcloud/l10n";
|
||||
import { getCurrentUser } from "@nextcloud/auth";
|
||||
import axios from "@nextcloud/axios";
|
||||
|
||||
import { API } from "../../services/API";
|
||||
import { IPhoto } from "../../types";
|
||||
import { PsContent, PsEvent, PsSlide } from "./types";
|
||||
|
||||
import Player from "video.js/dist/types/player";
|
||||
import { QualityLevelList } from "videojs-contrib-quality-levels";
|
||||
|
||||
type VideoContent = PsContent & {
|
||||
videoElement: HTMLVideoElement;
|
||||
videojs: Player & {
|
||||
qualityLevels?: () => QualityLevelList;
|
||||
};
|
||||
plyr: globalThis.Plyr;
|
||||
};
|
||||
|
||||
type PsVideoEvent = PsEvent & {
|
||||
content: VideoContent;
|
||||
};
|
||||
|
||||
const config_noTranscode = loadState(
|
||||
"memories",
|
||||
|
@ -21,16 +36,18 @@ const config_video_default_quality = Number(
|
|||
|
||||
/**
|
||||
* Check if slide has video content
|
||||
*
|
||||
* @param {Slide|Content} content Slide or Content object
|
||||
* @returns Boolean
|
||||
*/
|
||||
export function isVideoContent(content): boolean {
|
||||
export function isVideoContent(content: PsSlide | PsContent): boolean {
|
||||
return content?.data?.type === "video";
|
||||
}
|
||||
|
||||
class VideoContentSetup {
|
||||
constructor(lightbox: PhotoSwipe, private options) {
|
||||
constructor(
|
||||
lightbox: PhotoSwipe,
|
||||
private options: {
|
||||
preventDragOffset: number;
|
||||
}
|
||||
) {
|
||||
this.initLightboxEvents(lightbox);
|
||||
lightbox.on("init", () => {
|
||||
this.initPswpEvents(lightbox);
|
||||
|
@ -53,10 +70,6 @@ class VideoContentSetup {
|
|||
"useContentPlaceholder",
|
||||
this.useContentPlaceholder.bind(this)
|
||||
);
|
||||
|
||||
lightbox.addFilter("domItemData", (itemData, element, linkEl) => {
|
||||
return itemData;
|
||||
});
|
||||
}
|
||||
|
||||
initPswpEvents(pswp: PhotoSwipe) {
|
||||
|
@ -101,22 +114,19 @@ class VideoContentSetup {
|
|||
});
|
||||
|
||||
pswp.on("close", () => {
|
||||
if (isVideoContent(pswp.currSlide.content)) {
|
||||
// prevent more requests
|
||||
this.destroyVideo(pswp.currSlide.content);
|
||||
}
|
||||
this.destroyVideo(pswp.currSlide.content as VideoContent);
|
||||
});
|
||||
|
||||
// Prevent closing when video fullscreen is active
|
||||
pswp.on("pointerMove", (e) => {
|
||||
const plyr: Plyr = (<any>pswp.currSlide.content)?.plyr;
|
||||
const plyr = (<VideoContent>pswp.currSlide.content)?.plyr;
|
||||
if (plyr?.fullscreen.active) {
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getHLSsrc(content: any) {
|
||||
getHLSsrc(content: VideoContent) {
|
||||
// Get base URL
|
||||
const fileid = content.data.photo.fileid;
|
||||
return {
|
||||
|
@ -125,13 +135,13 @@ class VideoContentSetup {
|
|||
};
|
||||
}
|
||||
|
||||
async initVideo(content: any) {
|
||||
async initVideo(content: VideoContent) {
|
||||
if (!isVideoContent(content) || content.videojs || !config_videoIsSetup) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Prevent double loading
|
||||
content.videojs = {};
|
||||
content.videojs = {} as any;
|
||||
|
||||
// Load videojs scripts
|
||||
if (!globalThis.vidjs) {
|
||||
|
@ -142,20 +152,18 @@ class VideoContentSetup {
|
|||
content.videoElement = document.createElement("video");
|
||||
content.videoElement.className = "video-js";
|
||||
content.videoElement.setAttribute("poster", content.data.msrc);
|
||||
if (this.options.videoAttributes) {
|
||||
for (let key in this.options.videoAttributes) {
|
||||
content.videoElement.setAttribute(
|
||||
key,
|
||||
this.options.videoAttributes[key] || ""
|
||||
);
|
||||
}
|
||||
}
|
||||
content.videoElement.setAttribute("preload", "none");
|
||||
content.videoElement.setAttribute("controls", "");
|
||||
content.videoElement.setAttribute("playsinline", "");
|
||||
|
||||
// Add the video element to the actual container
|
||||
content.element.appendChild(content.videoElement);
|
||||
|
||||
// Create hls sources if enabled
|
||||
let sources: any[] = [];
|
||||
const sources: {
|
||||
src: string;
|
||||
type: string;
|
||||
}[] = [];
|
||||
|
||||
if (!config_noTranscode) {
|
||||
sources.push(this.getHLSsrc(content));
|
||||
|
@ -239,7 +247,7 @@ class VideoContentSetup {
|
|||
}
|
||||
}
|
||||
|
||||
destroyVideo(content: any) {
|
||||
destroyVideo(content: VideoContent) {
|
||||
if (isVideoContent(content)) {
|
||||
content.videojs?.dispose?.();
|
||||
content.videojs = null;
|
||||
|
@ -256,7 +264,7 @@ class VideoContentSetup {
|
|||
}
|
||||
}
|
||||
|
||||
initPlyr(content: any) {
|
||||
initPlyr(content: VideoContent) {
|
||||
if (content.plyr) return;
|
||||
|
||||
content.videoElement = content.videojs?.el()?.querySelector("video");
|
||||
|
@ -323,7 +331,7 @@ class VideoContentSetup {
|
|||
}
|
||||
|
||||
// Set the source to the original video
|
||||
if (content.videojs.src().includes("m3u8")) {
|
||||
if (content.videojs.src(undefined).includes("m3u8")) {
|
||||
content.videojs.src({
|
||||
src: content.data.src,
|
||||
type: "video/mp4",
|
||||
|
@ -332,7 +340,7 @@ class VideoContentSetup {
|
|||
return;
|
||||
} else {
|
||||
// Set source to HLS
|
||||
if (!content.videojs.src().includes("m3u8")) {
|
||||
if (!content.videojs.src(undefined).includes("m3u8")) {
|
||||
content.videojs.src(this.getHLSsrc(content));
|
||||
}
|
||||
}
|
||||
|
@ -402,16 +410,16 @@ class VideoContentSetup {
|
|||
}
|
||||
}
|
||||
|
||||
updateRotation(content: any, val?: number): boolean {
|
||||
updateRotation(content: VideoContent, val?: number): boolean {
|
||||
if (!content.videojs) return;
|
||||
|
||||
content.videoElement = content.videojs.el()?.querySelector("video");
|
||||
if (!content.videoElement) return;
|
||||
|
||||
const photo: IPhoto = content.data.photo;
|
||||
const photo = content.data.photo;
|
||||
const exif = photo.imageInfo?.exif;
|
||||
const rotation = val ?? Number(exif?.Rotation || 0);
|
||||
const shouldRotate = content.videojs?.src().includes("m3u8");
|
||||
const shouldRotate = content.videojs?.src(undefined).includes("m3u8");
|
||||
|
||||
if (rotation && shouldRotate) {
|
||||
let transform = `rotate(${rotation}deg)`;
|
||||
|
@ -437,7 +445,7 @@ class VideoContentSetup {
|
|||
return false;
|
||||
}
|
||||
|
||||
onContentDestroy({ content }) {
|
||||
onContentDestroy({ content }: PsVideoEvent) {
|
||||
this.destroyVideo(content);
|
||||
}
|
||||
|
||||
|
@ -466,25 +474,25 @@ class VideoContentSetup {
|
|||
}
|
||||
}
|
||||
|
||||
isContentZoomable(isZoomable: boolean, content) {
|
||||
isContentZoomable(isZoomable: boolean, content: PsContent) {
|
||||
return !isVideoContent(content) && isZoomable;
|
||||
}
|
||||
|
||||
isKeepingPlaceholder(keep: boolean, content) {
|
||||
isKeepingPlaceholder(keep: boolean, content: PsContent) {
|
||||
return isVideoContent(content) || keep;
|
||||
}
|
||||
|
||||
onContentActivate({ content }) {
|
||||
onContentActivate({ content }: PsVideoEvent) {
|
||||
this.initVideo(content);
|
||||
}
|
||||
|
||||
onContentDeactivate({ content }) {
|
||||
onContentDeactivate({ content }: PsVideoEvent) {
|
||||
this.destroyVideo(content);
|
||||
}
|
||||
|
||||
onContentLoad(e) {
|
||||
const content = e.content;
|
||||
if (!isVideoContent(e.content)) return;
|
||||
onContentLoad(e: PsVideoEvent) {
|
||||
const content: PsContent = e.content;
|
||||
if (!isVideoContent(content)) return;
|
||||
|
||||
// Stop default content load
|
||||
e.preventDefault();
|
||||
|
@ -508,7 +516,7 @@ class VideoContentSetup {
|
|||
content.onLoaded();
|
||||
}
|
||||
|
||||
useContentPlaceholder(usePlaceholder: boolean, content: any) {
|
||||
useContentPlaceholder(usePlaceholder: boolean, content: PsContent) {
|
||||
return isVideoContent(content) || usePlaceholder;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -179,6 +179,7 @@
|
|||
import { defineComponent } from "vue";
|
||||
|
||||
import { IDay, IPhoto, IRow, IRowType } from "../../types";
|
||||
import { PsSlide } from "./types";
|
||||
|
||||
import UserConfig from "../../mixins/UserConfig";
|
||||
import NcActions from "@nextcloud/vue/dist/Components/NcActions";
|
||||
|
@ -584,19 +585,17 @@ export default defineComponent({
|
|||
});
|
||||
|
||||
// Video support
|
||||
this.psVideo = new PsVideo(<any>this.photoswipe, {
|
||||
videoAttributes: { controls: "", playsinline: "", preload: "none" },
|
||||
autoplay: true,
|
||||
this.psVideo = new PsVideo(<PhotoSwipe>this.photoswipe, {
|
||||
preventDragOffset: 40,
|
||||
});
|
||||
|
||||
// Image support
|
||||
this.psImage = new PsImage(<any>this.photoswipe);
|
||||
this.psImage = new PsImage(<PhotoSwipe>this.photoswipe);
|
||||
|
||||
// Live Photo support
|
||||
this.psLivePhoto = new PsLivePhoto(
|
||||
<any>this.photoswipe,
|
||||
<any>this.psImage
|
||||
<PhotoSwipe>this.photoswipe,
|
||||
<PsImage>this.psImage
|
||||
);
|
||||
|
||||
// Patch the close button to stop the slideshow
|
||||
|
@ -979,7 +978,7 @@ export default defineComponent({
|
|||
|
||||
/** Play the current live photo */
|
||||
playLivePhoto() {
|
||||
this.psLivePhoto.onContentActivate(this.photoswipe.currSlide);
|
||||
this.psLivePhoto.onContentActivate(this.photoswipe.currSlide as PsSlide);
|
||||
},
|
||||
|
||||
/** Is the current photo a favorite */
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
import Content from "photoswipe/dist/types/slide/content";
|
||||
import Slide, { _SlideData } from "photoswipe/dist/types/slide/slide";
|
||||
import { IPhoto } from "../../types";
|
||||
|
||||
type PsAugment = {
|
||||
data: _SlideData & {
|
||||
photo?: IPhoto;
|
||||
};
|
||||
};
|
||||
export type PsSlide = Slide & PsAugment;
|
||||
export type PsContent = Content & PsAugment;
|
||||
export type PsEvent = {
|
||||
content: PsContent;
|
||||
preventDefault: () => void;
|
||||
};
|
Loading…
Reference in New Issue