Compare commits
1 commit
develop
...
feature/vi
Author | SHA1 | Date | |
---|---|---|---|
|
baeba38acb |
20 changed files with 1639 additions and 123 deletions
1
assets/static/video/plyr.svg
Normal file
1
assets/static/video/plyr.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 5.6 KiB |
33
frontend/package-lock.json
generated
33
frontend/package-lock.json
generated
|
@ -64,6 +64,7 @@
|
||||||
"mocha": "^10.2.0",
|
"mocha": "^10.2.0",
|
||||||
"node-storage-shim": "^2.0.1",
|
"node-storage-shim": "^2.0.1",
|
||||||
"photoswipe": "^4.1.3",
|
"photoswipe": "^4.1.3",
|
||||||
|
"plyr": "^3.7.8",
|
||||||
"postcss": "^8.4.20",
|
"postcss": "^8.4.20",
|
||||||
"postcss-import": "^15.1.0",
|
"postcss-import": "^15.1.0",
|
||||||
"postcss-loader": "^7.0.2",
|
"postcss-loader": "^7.0.2",
|
||||||
|
@ -4656,6 +4657,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz",
|
||||||
"integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg=="
|
"integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg=="
|
||||||
},
|
},
|
||||||
|
"node_modules/custom-event-polyfill": {
|
||||||
|
"version": "1.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/custom-event-polyfill/-/custom-event-polyfill-1.0.7.tgz",
|
||||||
|
"integrity": "sha512-TDDkd5DkaZxZFM8p+1I3yAlvM3rSr1wbrOliG4yJiwinMZN8z/iGL7BTlDkrJcYTmgUSb4ywVCc3ZaUtOtC76w=="
|
||||||
|
},
|
||||||
"node_modules/date-format": {
|
"node_modules/date-format": {
|
||||||
"version": "4.0.14",
|
"version": "4.0.14",
|
||||||
"resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz",
|
"resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz",
|
||||||
|
@ -8390,6 +8396,11 @@
|
||||||
"json5": "lib/cli.js"
|
"json5": "lib/cli.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/loadjs": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/loadjs/-/loadjs-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-AgQGZisAlTPbTEzrHPb6q+NYBMD+DP9uvGSIjSUM5uG+0jG15cb8axWpxuOIqrmQjn6scaaH8JwloiP27b2KXA=="
|
||||||
|
},
|
||||||
"node_modules/loadware": {
|
"node_modules/loadware": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/loadware/-/loadware-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/loadware/-/loadware-2.0.0.tgz",
|
||||||
|
@ -9525,6 +9536,18 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/plyr": {
|
||||||
|
"version": "3.7.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/plyr/-/plyr-3.7.8.tgz",
|
||||||
|
"integrity": "sha512-yG/EHDobwbB/uP+4Bm6eUpJ93f8xxHjjk2dYcD1Oqpe1EcuQl5tzzw9Oq+uVAzd2lkM11qZfydSiyIpiB8pgdA==",
|
||||||
|
"dependencies": {
|
||||||
|
"core-js": "^3.26.1",
|
||||||
|
"custom-event-polyfill": "^1.0.7",
|
||||||
|
"loadjs": "^4.2.0",
|
||||||
|
"rangetouch": "^2.0.1",
|
||||||
|
"url-polyfill": "^1.1.12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/pofile": {
|
"node_modules/pofile": {
|
||||||
"version": "1.1.4",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/pofile/-/pofile-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/pofile/-/pofile-1.1.4.tgz",
|
||||||
|
@ -10942,6 +10965,11 @@
|
||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/rangetouch": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/rangetouch/-/rangetouch-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-sln+pNSc8NGaHoLzwNBssFSf/rSYkqeBXzX1AtJlkJiUaVSJSbRAWJk+4omsXkN+EJalzkZhWQ3th1m0FpR5xA=="
|
||||||
|
},
|
||||||
"node_modules/raw-body": {
|
"node_modules/raw-body": {
|
||||||
"version": "2.5.2",
|
"version": "2.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
|
||||||
|
@ -12661,6 +12689,11 @@
|
||||||
"url": "https://opencollective.com/webpack"
|
"url": "https://opencollective.com/webpack"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/url-polyfill": {
|
||||||
|
"version": "1.1.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/url-polyfill/-/url-polyfill-1.1.12.tgz",
|
||||||
|
"integrity": "sha512-mYFmBHCapZjtcNHW0MDq9967t+z4Dmg5CJ0KqysK3+ZbyoNOWQHksGCTWwDhxGXllkWlOc10Xfko6v4a3ucM6A=="
|
||||||
|
},
|
||||||
"node_modules/util": {
|
"node_modules/util": {
|
||||||
"version": "0.12.5",
|
"version": "0.12.5",
|
||||||
"resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz",
|
"resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz",
|
||||||
|
|
|
@ -75,6 +75,7 @@
|
||||||
"mocha": "^10.2.0",
|
"mocha": "^10.2.0",
|
||||||
"node-storage-shim": "^2.0.1",
|
"node-storage-shim": "^2.0.1",
|
||||||
"photoswipe": "^4.1.3",
|
"photoswipe": "^4.1.3",
|
||||||
|
"plyr": "^3.7.8",
|
||||||
"postcss": "^8.4.20",
|
"postcss": "^8.4.20",
|
||||||
"postcss-import": "^15.1.0",
|
"postcss-import": "^15.1.0",
|
||||||
"postcss-loader": "^7.0.2",
|
"postcss-loader": "^7.0.2",
|
||||||
|
|
|
@ -76,9 +76,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="player.show" class="video-viewer" @click.stop.prevent="closePlayer" @keydown.esc.stop.prevent="closePlayer">
|
<div v-if="player.show" class="video-viewer" @click.stop.prevent="closePlayer" @keydown.esc.stop.prevent="closePlayer">
|
||||||
<p-video-player ref="player" :source="player.source" :poster="player.poster"
|
<p-video-player ref="player" :videos="videos" :index="0" @close="closePlayer"></p-video-player>
|
||||||
:height="player.height" :width="player.width" :autoplay="player.autoplay" :loop="player.loop" @close="closePlayer">
|
|
||||||
</p-video-player>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -101,13 +99,14 @@ export default {
|
||||||
canDownload: this.$config.allow("photos", "download") && this.$config.feature("download"),
|
canDownload: this.$config.allow("photos", "download") && this.$config.feature("download"),
|
||||||
selection: this.$clipboard.selection,
|
selection: this.$clipboard.selection,
|
||||||
config: this.$config.values,
|
config: this.$config.values,
|
||||||
item: new Thumb(),
|
item: new Thumb(false),
|
||||||
subscriptions: [],
|
subscriptions: [],
|
||||||
interval: false,
|
interval: false,
|
||||||
slideshow: {
|
slideshow: {
|
||||||
active: false,
|
active: false,
|
||||||
next: 0,
|
next: 0,
|
||||||
},
|
},
|
||||||
|
videos: [],
|
||||||
player: {
|
player: {
|
||||||
show: false,
|
show: false,
|
||||||
loop: false,
|
loop: false,
|
||||||
|
@ -175,28 +174,23 @@ export default {
|
||||||
},
|
},
|
||||||
onPlay() {
|
onPlay() {
|
||||||
if (this.item && this.item.Playable) {
|
if (this.item && this.item.Playable) {
|
||||||
new Photo().find(this.item.UID).then((video) => this.openPlayer(video));
|
new Photo().find(this.item.UID).then((photo) => this.openPlayer(photo));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
openPlayer(video) {
|
openPlayer(photo) {
|
||||||
if (!video) {
|
if (!photo) {
|
||||||
this.$notify.error(this.$gettext("No video selected"));
|
this.$notify.error(this.$gettext("No video selected"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const params = video.videoParams();
|
const video = photo.video();
|
||||||
|
|
||||||
if (params.error) {
|
if (!video || video.Error) {
|
||||||
this.$notify.error(params.error);
|
this.$notify.error(this.$gettext("Not Found"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set video parameters.
|
this.videos = [video];
|
||||||
this.player.loop = params.loop;
|
|
||||||
this.player.width = params.width;
|
|
||||||
this.player.height = params.height;
|
|
||||||
this.player.poster = params.poster;
|
|
||||||
this.player.source = params.uri;
|
|
||||||
|
|
||||||
// Play video.
|
// Play video.
|
||||||
this.player.show = true;
|
this.player.show = true;
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="video-wrapper" :style="style">
|
<div class="video-wrapper" :style="style" @click.stop.prevent>
|
||||||
<video :key="source" ref="video" class="video-player" :height="height" :width="width" :autoplay="autoplay"
|
<video :key="source" ref="video" class="video-player"
|
||||||
:style="style" :poster="poster" :loop="loop" preload="auto" controls playsinline @click.stop
|
preload="auto" controls autoplay playsinline @click.stop
|
||||||
@keydown.esc.stop.prevent="$emit('close')">
|
@keydown.esc.stop.prevent="$emit('close')">
|
||||||
<source :src="source">
|
<source :src="video.url()">
|
||||||
</video>
|
</video>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import Video from "model/video";
|
||||||
|
import Plyr from 'plyr';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "PVideoPlayer",
|
name: "PVideoPlayer",
|
||||||
props: {
|
props: {
|
||||||
|
@ -19,13 +22,13 @@ export default {
|
||||||
},
|
},
|
||||||
poster: {
|
poster: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: false,
|
||||||
default: ""
|
default: ""
|
||||||
},
|
},
|
||||||
source: {
|
index: {
|
||||||
type: String,
|
type: Number,
|
||||||
required: true,
|
required: false,
|
||||||
default: ""
|
default: 0
|
||||||
},
|
},
|
||||||
width: {
|
width: {
|
||||||
type: Number,
|
type: Number,
|
||||||
|
@ -59,38 +62,127 @@ export default {
|
||||||
error: {
|
error: {
|
||||||
type: Function,
|
type: Function,
|
||||||
default: () => {},
|
default: () => {},
|
||||||
}
|
|
||||||
},
|
},
|
||||||
data: () => ({
|
videos: {
|
||||||
|
type: Array,
|
||||||
|
required: false,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
album: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
const c = this.$config;
|
||||||
|
|
||||||
|
return {
|
||||||
refresh: false,
|
refresh: false,
|
||||||
style: `width: 90vw; height: 90vh`,
|
style: `width: 90vw; height: 90vh`,
|
||||||
}),
|
source: "",
|
||||||
|
video: new Video(false),
|
||||||
|
player: false,
|
||||||
|
current: this.index,
|
||||||
|
options: {
|
||||||
|
iconUrl: `${c.staticUri}/video/plyr.svg`,
|
||||||
|
controls: ['play-large', 'play', 'progress', 'current-time', 'duration', 'mute', 'volume', 'download', 'settings', 'airplay', 'fullscreen'],
|
||||||
|
settings: ['loop'],
|
||||||
|
captions: { active: false, language: 'auto', update: false },
|
||||||
|
hideControls: true,
|
||||||
|
enabled: true,
|
||||||
|
autoplay: true,
|
||||||
|
clickToPlay: true,
|
||||||
|
disableContextMenu: false,
|
||||||
|
resetOnEnd: true,
|
||||||
|
toggleInvert: true,
|
||||||
|
blankVideo: `${c.staticUri}/video/404.mp4`,
|
||||||
|
loop: {
|
||||||
|
active: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
watch: {
|
watch: {
|
||||||
source: function (src) {
|
source: function (src) {
|
||||||
if (src) {
|
if (src) {
|
||||||
this.setSrc(src);
|
this.setSrc(src);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
videos: function () {
|
||||||
|
this.onVideos();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
document.body.classList.add("player");
|
document.body.classList.add("player");
|
||||||
this.render();
|
this.render();
|
||||||
|
window.addEventListener('keyup', this.onKeyUp);
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
document.body.classList.remove("player");
|
document.body.classList.remove("player");
|
||||||
this.stop();
|
this.stop();
|
||||||
},
|
},
|
||||||
|
beforeUnmount() {
|
||||||
|
try {
|
||||||
|
if (this.player) {
|
||||||
|
this.player.destroy();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
window.removeEventListener('keyup', this.onKeyUp);
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
videoEl() {
|
videoEl() {
|
||||||
return this.$el.getElementsByTagName('video')[0];
|
return this.$el.getElementsByTagName('video')[0];
|
||||||
},
|
},
|
||||||
updateStyle() {
|
updateStyle() {
|
||||||
// this.style = `width: ${this.width.toString()}px; height: ${this.height.toString()}px`;
|
if (!this.video || !this.video.Width) {
|
||||||
this.style = `width:100%; height: 100%`;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size = this.video.playerSize();
|
||||||
|
|
||||||
|
this.style = `width: ${size.width.toString()}px; height: ${size.height.toString()}px`;
|
||||||
|
const plyrEl = this.$el.getElementsByClassName('plyr')[0];
|
||||||
|
if (plyrEl) {
|
||||||
|
plyrEl.style.cssText = this.style;
|
||||||
|
}
|
||||||
this.$el.style.cssText = this.style;
|
this.$el.style.cssText = this.style;
|
||||||
},
|
},
|
||||||
|
currentVideo() {
|
||||||
|
if(typeof this.videos[this.current] === 'undefined') {
|
||||||
|
return Video.notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.videos[this.current];
|
||||||
|
},
|
||||||
|
onVideos() {
|
||||||
|
this.current = this.play;
|
||||||
|
/*
|
||||||
|
const video = this.currentVideo();
|
||||||
|
|
||||||
|
if (!video || video.Error) {
|
||||||
|
this.$notify.error(this.$gettext("Not Found"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set video parameters.
|
||||||
|
const size = video.playerSize();
|
||||||
|
this.loop = video.loop();
|
||||||
|
this.width = size.width;
|
||||||
|
this.height = size.height;
|
||||||
|
this.poster = video.posterUrl();
|
||||||
|
this.source = video.url(); */
|
||||||
|
},
|
||||||
render() {
|
render() {
|
||||||
this.updateStyle();
|
const el = this.videoEl();
|
||||||
|
if (!el) return;
|
||||||
|
|
||||||
|
this.player = new Plyr(el, this.options);
|
||||||
|
// this.player.on("ended", (ev) => { console.log("event.ended", ev); });
|
||||||
|
|
||||||
|
this.play();
|
||||||
},
|
},
|
||||||
fullscreen() {
|
fullscreen() {
|
||||||
const el = this.videoEl();
|
const el = this.videoEl();
|
||||||
|
@ -98,12 +190,65 @@ export default {
|
||||||
|
|
||||||
el.requestFullscreen();
|
el.requestFullscreen();
|
||||||
},
|
},
|
||||||
setSrc(src) {
|
play() {
|
||||||
if (!src) {
|
this.video = this.currentVideo();
|
||||||
|
|
||||||
|
if (!this.video) {
|
||||||
|
console.log("render: No current video");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateStyle();
|
this.updateStyle();
|
||||||
|
this.player.source = {
|
||||||
|
type: 'video',
|
||||||
|
title: this.video.Title,
|
||||||
|
sources: [
|
||||||
|
{
|
||||||
|
src: this.video.url(),
|
||||||
|
// type: this.video.Mime,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
poster: this.video.posterUrl("fit_720"),
|
||||||
|
};
|
||||||
|
this.player.loop = this.videos.length === 0 && this.video.loop();
|
||||||
|
this.player.play();
|
||||||
|
},
|
||||||
|
onPrev(ev) {
|
||||||
|
if(this.videos.length < 2) {
|
||||||
|
this.current = 0;
|
||||||
|
return;
|
||||||
|
} else if(this.current <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.player.stop();
|
||||||
|
this.current--;
|
||||||
|
this.play();
|
||||||
|
},
|
||||||
|
onNext(ev) {
|
||||||
|
if(this.videos.length < 2) {
|
||||||
|
this.current = 0;
|
||||||
|
return;
|
||||||
|
} else if(this.current >= this.videos.length - 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.player.stop();
|
||||||
|
this.current++;
|
||||||
|
this.play();
|
||||||
|
},
|
||||||
|
onKeyUp(ev) {
|
||||||
|
switch(ev.key) {
|
||||||
|
case "Escape": this.$emit('close'); break;
|
||||||
|
case "ArrowLeft": this.onPrev(ev); break;
|
||||||
|
case "ArrowRight": this.onNext(ev); break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setSrc(src) {
|
||||||
|
// console.log("setSrc", src);
|
||||||
|
if (!src) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const el = this.videoEl();
|
const el = this.videoEl();
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
|
@ -111,6 +256,17 @@ export default {
|
||||||
el.src = src;
|
el.src = src;
|
||||||
el.poster = this.poster;
|
el.poster = this.poster;
|
||||||
el.play();
|
el.play();
|
||||||
|
|
||||||
|
this.updateStyle();
|
||||||
|
|
||||||
|
// console.log("el", el);
|
||||||
|
|
||||||
|
/* this.player = new Plyr(el);
|
||||||
|
console.log("this.player", this.player);
|
||||||
|
this.player.play();
|
||||||
|
this.player.source = src;
|
||||||
|
this.player.poster = this.poster;
|
||||||
|
*/
|
||||||
},
|
},
|
||||||
pause() {
|
pause() {
|
||||||
const el = this.videoEl();
|
const el = this.videoEl();
|
||||||
|
|
|
@ -26,6 +26,8 @@ Additional information can be found in our Developer Guide:
|
||||||
@import url("vendor/icons/material-design-icons.css");
|
@import url("vendor/icons/material-design-icons.css");
|
||||||
@import url("../../node_modules/vuetify/dist/vuetify.min.css");
|
@import url("../../node_modules/vuetify/dist/vuetify.min.css");
|
||||||
@import url("../../node_modules/maplibre-gl/dist/maplibre-gl.css");
|
@import url("../../node_modules/maplibre-gl/dist/maplibre-gl.css");
|
||||||
|
@import url("../../node_modules/plyr/dist/plyr.css");
|
||||||
|
@import url("variables.css");
|
||||||
@import url("typography.css");
|
@import url("typography.css");
|
||||||
@import url("wallpapers.css");
|
@import url("wallpapers.css");
|
||||||
@import url("scrollbar.css");
|
@import url("scrollbar.css");
|
||||||
|
|
4
frontend/src/css/variables.css
Normal file
4
frontend/src/css/variables.css
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
:root {
|
||||||
|
--plyr-color-main: #2f303164; /* #d3cbfc66; */
|
||||||
|
--plyr-video-control-background-hover: #2f303190;
|
||||||
|
}
|
|
@ -10,7 +10,7 @@
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
background-color: rgba(0,0,0,1);
|
background-color: rgba(0,0,0,0.74);
|
||||||
}
|
}
|
||||||
|
|
||||||
#photoprism .video-wrapper {
|
#photoprism .video-wrapper {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-if="show" class="video-viewer" role="dialog" @click.stop.prevent="onClose" @keydown.esc.stop.prevent="onClose">
|
<div v-if="show" class="video-viewer" role="dialog" @click.stop.prevent="onClose" @keydown.esc.stop.prevent="onClose">
|
||||||
<p-video-player v-show="show" ref="player" :source="source" :poster="poster" :height="height"
|
<p-video-player v-show="show" ref="player" :videos="videos" :index="index" :album="album" @close="onClose"></p-video-player>
|
||||||
:width="width" :autoplay="true" :loop="loop" @close="onClose"></p-video-player>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
|
@ -18,7 +17,8 @@ export default {
|
||||||
defaultHeight: 480,
|
defaultHeight: 480,
|
||||||
width: 640,
|
width: 640,
|
||||||
height: 480,
|
height: 480,
|
||||||
video: null,
|
index: 0,
|
||||||
|
videos: [],
|
||||||
album: null,
|
album: null,
|
||||||
loop: false,
|
loop: false,
|
||||||
subscriptions: [],
|
subscriptions: [],
|
||||||
|
@ -39,9 +39,16 @@ export default {
|
||||||
methods: {
|
methods: {
|
||||||
onOpen(ev, params) {
|
onOpen(ev, params) {
|
||||||
const fullscreen = !!params.fullscreen;
|
const fullscreen = !!params.fullscreen;
|
||||||
|
const hasQueue = params.videos && params.videos.length > 0;
|
||||||
|
|
||||||
this.video = params.video;
|
this.videos = hasQueue ? params.videos : [];
|
||||||
this.album = params.album;
|
this.album = params.album ? params.album : null;
|
||||||
|
|
||||||
|
if(params.index && params.index < this.videos.length) {
|
||||||
|
this.index = params.index;
|
||||||
|
} else {
|
||||||
|
this.index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
this.play(fullscreen);
|
this.play(fullscreen);
|
||||||
},
|
},
|
||||||
|
@ -58,25 +65,11 @@ export default {
|
||||||
this.show = false;
|
this.show = false;
|
||||||
},
|
},
|
||||||
play(fullscreen) {
|
play(fullscreen) {
|
||||||
if (!this.video) {
|
if (!this.videos) {
|
||||||
this.$notify.error(this.$gettext("No video selected"));
|
this.$notify.error(this.$gettext("No videos found to play"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const params = this.video.videoParams();
|
|
||||||
|
|
||||||
if (params.error) {
|
|
||||||
this.$notify.error(params.error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set video parameters.
|
|
||||||
this.loop = params.loop;
|
|
||||||
this.width = params.width;
|
|
||||||
this.height = params.height;
|
|
||||||
this.poster = params.poster;
|
|
||||||
this.source = params.uri;
|
|
||||||
|
|
||||||
// Play video.
|
// Play video.
|
||||||
this.show = true;
|
this.show = true;
|
||||||
|
|
||||||
|
|
|
@ -24,13 +24,14 @@ Additional information can be found in our Developer Guide:
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import RestModel from "model/rest";
|
import RestModel from "model/rest";
|
||||||
|
import Video from "model/video";
|
||||||
|
import { MediaAnimated, MediaImage } from "model/photo";
|
||||||
import Api from "common/api";
|
import Api from "common/api";
|
||||||
import { DateTime } from "luxon";
|
import { DateTime } from "luxon";
|
||||||
import Util from "common/util";
|
import Util from "common/util";
|
||||||
import { config } from "app/session";
|
import { config } from "app/session";
|
||||||
import { $gettext } from "common/vm";
|
import { $gettext } from "common/vm";
|
||||||
import download from "common/download";
|
import download from "common/download";
|
||||||
import { MediaImage } from "./photo";
|
|
||||||
|
|
||||||
export class File extends RestModel {
|
export class File extends RestModel {
|
||||||
getDefaults() {
|
getDefaults() {
|
||||||
|
@ -128,7 +129,7 @@ export class File extends RestModel {
|
||||||
return `${config.contentUri}/t/${this.Hash}/${config.previewToken}/${size}`;
|
return `${config.contentUri}/t/${this.Hash}/${config.previewToken}/${size}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
getDownloadUrl() {
|
downloadUrl() {
|
||||||
return `${config.apiUri}/dl/${this.Hash}?t=${config.downloadToken}`;
|
return `${config.apiUri}/dl/${this.Hash}?t=${config.downloadToken}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,7 +138,7 @@ export class File extends RestModel {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
download(this.getDownloadUrl(), this.baseName(this.Name));
|
download(this.downloadUrl(), this.baseName(this.Name));
|
||||||
}
|
}
|
||||||
|
|
||||||
calculateSize(width, height) {
|
calculateSize(width, height) {
|
||||||
|
@ -287,6 +288,48 @@ export class File extends RestModel {
|
||||||
return Api.delete(this.getPhotoResource() + "/like");
|
return Api.delete(this.getPhotoResource() + "/like");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isPlayable() {
|
||||||
|
if (this.MediaType === MediaAnimated) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.Video;
|
||||||
|
}
|
||||||
|
|
||||||
|
video() {
|
||||||
|
let width = this.Width;
|
||||||
|
let height = this.Height;
|
||||||
|
|
||||||
|
if (width <= 0 || height <= 0) {
|
||||||
|
width = 640;
|
||||||
|
height = 480;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Video({
|
||||||
|
UID: this.UID,
|
||||||
|
PhotoUID: this.PhotoUID,
|
||||||
|
Hash: this.Hash,
|
||||||
|
PosterHash: this.Hash,
|
||||||
|
Title: this.Name,
|
||||||
|
Description: "",
|
||||||
|
TakenAt: this.TakenAt,
|
||||||
|
Favorite: false,
|
||||||
|
Playable: this.isPlayable(),
|
||||||
|
HDR: this.HDR,
|
||||||
|
Mime: this.Mime,
|
||||||
|
Type: this.Video ? "video" : "animated",
|
||||||
|
Codec: this.Codec,
|
||||||
|
Width: width,
|
||||||
|
Height: height,
|
||||||
|
Duration: this.Duration,
|
||||||
|
FPS: this.FPS,
|
||||||
|
Frames: this.Frames,
|
||||||
|
Projection: this.Projection,
|
||||||
|
ColorProfile: this.ColorProfile,
|
||||||
|
Error: this.Error,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
static getCollectionResource() {
|
static getCollectionResource() {
|
||||||
return "files";
|
return "files";
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ import memoizeOne from "memoize-one";
|
||||||
|
|
||||||
import RestModel from "model/rest";
|
import RestModel from "model/rest";
|
||||||
import File from "model/file";
|
import File from "model/file";
|
||||||
|
import Video from "model/video";
|
||||||
import Marker from "model/marker";
|
import Marker from "model/marker";
|
||||||
import Api from "common/api";
|
import Api from "common/api";
|
||||||
import { DateTime } from "luxon";
|
import { DateTime } from "luxon";
|
||||||
|
@ -396,13 +397,7 @@ export class Photo extends RestModel {
|
||||||
return files.some((f) => f.Video);
|
return files.some((f) => f.Video);
|
||||||
});
|
});
|
||||||
|
|
||||||
videoParams() {
|
video() {
|
||||||
const uri = this.videoUrl();
|
|
||||||
|
|
||||||
if (!uri) {
|
|
||||||
return { error: "no video selected" };
|
|
||||||
}
|
|
||||||
|
|
||||||
let main = this.mainFile();
|
let main = this.mainFile();
|
||||||
let file = this.videoFile();
|
let file = this.videoFile();
|
||||||
|
|
||||||
|
@ -410,44 +405,37 @@ export class Photo extends RestModel {
|
||||||
file = main;
|
file = main;
|
||||||
}
|
}
|
||||||
|
|
||||||
const vw = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
|
let width = file.Width > 0 ? file.Width : main.Width;
|
||||||
const vh = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
|
let height = file.Height > 0 ? file.Height : main.Height;
|
||||||
|
|
||||||
let actualWidth = 640;
|
if (width <= 0 || height <= 0) {
|
||||||
let actualHeight = 480;
|
width = 640;
|
||||||
|
height = 480;
|
||||||
if (file.Width > 0) {
|
|
||||||
actualWidth = file.Width;
|
|
||||||
} else if (main && main.Width > 0) {
|
|
||||||
actualWidth = main.Width;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file.Height > 0) {
|
return new Video({
|
||||||
actualHeight = file.Height;
|
UID: file.UID ? file.UID : this.FileUID,
|
||||||
} else if (main && main.Height > 0) {
|
PhotoUID: this.UID,
|
||||||
actualHeight = main.Height;
|
Hash: file.Hash ? file.Hash : this.Hash,
|
||||||
}
|
PosterHash: this.mainFileHash(),
|
||||||
|
Title: this.Title,
|
||||||
let width = actualWidth;
|
TakenAtLocal: this.TakenAtLocal,
|
||||||
let height = actualHeight;
|
Description: this.Description,
|
||||||
|
Favorite: this.Favorite,
|
||||||
if (vw < width + 80) {
|
Playable: this.isPlayable(),
|
||||||
let newWidth = vw - 90;
|
HDR: file.HDR,
|
||||||
height = Math.round(newWidth * (actualHeight / actualWidth));
|
Mime: file.Mime,
|
||||||
width = newWidth;
|
Type: this.Type,
|
||||||
}
|
Codec: file.Codec,
|
||||||
|
Width: width,
|
||||||
if (vh < height + 100) {
|
Height: height,
|
||||||
let newHeight = vh - 160;
|
Duration: file.Duration,
|
||||||
width = Math.round(newHeight * (actualWidth / actualHeight));
|
FPS: file.FPS,
|
||||||
height = newHeight;
|
Frames: file.Frames,
|
||||||
}
|
Projection: file.Projection,
|
||||||
|
ColorProfile: file.ColorProfile,
|
||||||
const loop = this.Type === MediaAnimated || (file.Duration >= 0 && file.Duration <= 5000000000);
|
Error: file.Error,
|
||||||
const poster = this.thumbnailUrl("fit_720");
|
});
|
||||||
const error = false;
|
|
||||||
|
|
||||||
return { width, height, loop, poster, uri, error };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
videoFile() {
|
videoFile() {
|
||||||
|
@ -600,7 +588,7 @@ export class Photo extends RestModel {
|
||||||
return `${contentUri}/t/${hash}/${previewToken}/${size}`;
|
return `${contentUri}/t/${hash}/${previewToken}/${size}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
getDownloadUrl() {
|
downloadUrl() {
|
||||||
return `${config.apiUri}/dl/${this.mainFileHash()}?t=${config.downloadToken}`;
|
return `${config.apiUri}/dl/${this.mainFileHash()}?t=${config.downloadToken}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -68,7 +68,7 @@ export class Thumb extends Model {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static thumbNotFound() {
|
static notFound() {
|
||||||
const result = {
|
const result = {
|
||||||
UID: "",
|
UID: "",
|
||||||
Title: $gettext("Not Found"),
|
Title: $gettext("Not Found"),
|
||||||
|
@ -112,7 +112,7 @@ export class Thumb extends Model {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!photo || !photo.Hash) {
|
if (!photo || !photo.Hash) {
|
||||||
return this.thumbNotFound();
|
return this.notFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = {
|
const result = {
|
||||||
|
@ -144,7 +144,7 @@ export class Thumb extends Model {
|
||||||
|
|
||||||
static fromFile(photo, file) {
|
static fromFile(photo, file) {
|
||||||
if (!photo || !file || !file.Hash) {
|
if (!photo || !file || !file.Hash) {
|
||||||
return this.thumbNotFound();
|
return this.notFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = {
|
const result = {
|
||||||
|
|
274
frontend/src/model/video.js
Normal file
274
frontend/src/model/video.js
Normal file
|
@ -0,0 +1,274 @@
|
||||||
|
/*
|
||||||
|
|
||||||
|
Copyright (c) 2018 - 2023 PhotoPrism UG. All rights reserved.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under Version 3 of the GNU Affero General Public License (the "AGPL"):
|
||||||
|
<https://docs.photoprism.app/license/agpl>
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
The AGPL is supplemented by our Trademark and Brand Guidelines,
|
||||||
|
which describe how our Brand Assets may be used:
|
||||||
|
<https://www.photoprism.app/trademark>
|
||||||
|
|
||||||
|
Feel free to send an email to hello@photoprism.app if you have questions,
|
||||||
|
want to support our work, or just want to say hello.
|
||||||
|
|
||||||
|
Additional information can be found in our Developer Guide:
|
||||||
|
<https://docs.photoprism.app/developer-guide/>
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Model from "model.js";
|
||||||
|
import Api from "common/api";
|
||||||
|
import { $gettext } from "common/vm";
|
||||||
|
import File from "model/file";
|
||||||
|
import Photo from "model/photo";
|
||||||
|
import {
|
||||||
|
CodecAv1,
|
||||||
|
CodecHvc1,
|
||||||
|
CodecOGV,
|
||||||
|
CodecVP8,
|
||||||
|
CodecVP9,
|
||||||
|
FormatAv1,
|
||||||
|
FormatAvc,
|
||||||
|
FormatHevc,
|
||||||
|
FormatWebM,
|
||||||
|
MediaAnimated,
|
||||||
|
} from "model/photo";
|
||||||
|
import { config } from "app/session";
|
||||||
|
import { canUseOGV, canUseVP8, canUseVP9, canUseAv1, canUseWebM, canUseHevc } from "common/caniuse";
|
||||||
|
|
||||||
|
export class Video extends Model {
|
||||||
|
getDefaults() {
|
||||||
|
return {
|
||||||
|
UID: "",
|
||||||
|
PhotoUID: "",
|
||||||
|
Hash: "",
|
||||||
|
PosterHash: "",
|
||||||
|
Title: "",
|
||||||
|
Description: "",
|
||||||
|
TakenAt: "",
|
||||||
|
Favorite: false,
|
||||||
|
Playable: false,
|
||||||
|
HDR: false,
|
||||||
|
Mime: "",
|
||||||
|
Type: "",
|
||||||
|
Codec: "",
|
||||||
|
Width: 640,
|
||||||
|
Height: 480,
|
||||||
|
Duration: 0,
|
||||||
|
FPS: 0,
|
||||||
|
Frames: 0,
|
||||||
|
Projection: "",
|
||||||
|
ColorProfile: "",
|
||||||
|
Error: "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getId() {
|
||||||
|
return this.UID ? this.UID : this.PhotoUID;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasId() {
|
||||||
|
return !!this.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleLike() {
|
||||||
|
this.Favorite = !this.Favorite;
|
||||||
|
|
||||||
|
if (this.Favorite) {
|
||||||
|
return Api.post("photos/" + this.PhotoUID + "/like");
|
||||||
|
} else {
|
||||||
|
return Api.delete("photos/" + this.PhotoUID + "/like");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
url() {
|
||||||
|
let hash = this.Hash ? this.Hash : this.PosterHash;
|
||||||
|
|
||||||
|
if (!hash) {
|
||||||
|
return `${config.staticUri}/video/404.mp4`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.Hash && (this.Codec || this.FileType)) {
|
||||||
|
let videoFormat = FormatAvc;
|
||||||
|
|
||||||
|
if (canUseHevc && this.Codec === CodecHvc1) {
|
||||||
|
videoFormat = FormatHevc;
|
||||||
|
} else if (canUseOGV && this.Codec === CodecOGV) {
|
||||||
|
videoFormat = CodecOGV;
|
||||||
|
} else if (canUseVP8 && this.Codec === CodecVP8) {
|
||||||
|
videoFormat = CodecVP8;
|
||||||
|
} else if (canUseVP9 && this.Codec === CodecVP9) {
|
||||||
|
videoFormat = CodecVP9;
|
||||||
|
} else if (canUseAv1 && this.Codec === CodecAv1) {
|
||||||
|
videoFormat = FormatAv1;
|
||||||
|
} else if (canUseWebM && this.FileType === FormatWebM) {
|
||||||
|
videoFormat = FormatWebM;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${config.videoUri}/videos/${hash}/${config.previewToken}/${videoFormat}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${config.videoUri}/videos/${hash}/${config.previewToken}/${FormatAvc}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadUrl() {
|
||||||
|
return `${config.apiUri}/dl/${this.Hash}?t=${config.downloadToken}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
posterUrl(size) {
|
||||||
|
let hash = this.PosterHash ? this.PosterHash : this.Hash;
|
||||||
|
|
||||||
|
if (!size) {
|
||||||
|
size = "fit_720";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hash) {
|
||||||
|
return `${config.contentUri}/svg/video`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${config.contentUri}/t/${hash}/${config.previewToken}/${size}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
loop() {
|
||||||
|
return this.Type === MediaAnimated || (this.Duration >= 0 && this.Duration <= 5000000000);
|
||||||
|
}
|
||||||
|
|
||||||
|
playerSize() {
|
||||||
|
const vw = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
|
||||||
|
const vh = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
|
||||||
|
|
||||||
|
let actualWidth = this.Width;
|
||||||
|
let actualHeight = this.Height;
|
||||||
|
|
||||||
|
let width = actualWidth;
|
||||||
|
let height = actualHeight;
|
||||||
|
|
||||||
|
if (vw < width + 70) {
|
||||||
|
let newWidth = vw - 80;
|
||||||
|
height = Math.round(newWidth * (actualHeight / actualWidth));
|
||||||
|
width = newWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vh < height + 100) {
|
||||||
|
let newHeight = vh - 140;
|
||||||
|
width = Math.round(newHeight * (actualWidth / actualHeight));
|
||||||
|
height = newHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!width || !height) {
|
||||||
|
width = 640;
|
||||||
|
height = 480;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { width, height };
|
||||||
|
}
|
||||||
|
|
||||||
|
static notFound() {
|
||||||
|
const result = new this();
|
||||||
|
result.Title = $gettext("Not Found");
|
||||||
|
result.Error = "not found";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromPhotos(photos, photosIndex) {
|
||||||
|
let videos = [];
|
||||||
|
let index = 0;
|
||||||
|
|
||||||
|
if (!photosIndex) {
|
||||||
|
photosIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const n = photos.length;
|
||||||
|
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
const video = this.fromPhoto(photos[i]);
|
||||||
|
if (video && !video.Error) {
|
||||||
|
videos.push(video);
|
||||||
|
if (photosIndex > i) {
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { videos, index };
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromPhoto(photo) {
|
||||||
|
if (!photo || !photo.Hash) {
|
||||||
|
return this.notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(photo instanceof Photo)) {
|
||||||
|
photo = new Photo(photo);
|
||||||
|
}
|
||||||
|
|
||||||
|
return photo.video();
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromFile(photo, file) {
|
||||||
|
if (!file || !file.Hash) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(file instanceof File)) {
|
||||||
|
file = new File(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!file.isPlayable()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const video = file.video();
|
||||||
|
|
||||||
|
if (photo) {
|
||||||
|
video.Title = photo.Title;
|
||||||
|
video.Description = photo.Description;
|
||||||
|
video.Favorite = photo.Favorite;
|
||||||
|
}
|
||||||
|
|
||||||
|
return video;
|
||||||
|
}
|
||||||
|
|
||||||
|
static wrap(data) {
|
||||||
|
return data.map((values) => new this(values));
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromFiles(photos) {
|
||||||
|
let result = [];
|
||||||
|
|
||||||
|
if (!photos || !photos.length) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const n = photos.length;
|
||||||
|
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
let p = photos[i];
|
||||||
|
|
||||||
|
if (!p.Files || !p.Files.length) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let j = 0; j < p.Files.length; j++) {
|
||||||
|
let f = p.Files[j];
|
||||||
|
|
||||||
|
let video = this.fromFile(p, f);
|
||||||
|
|
||||||
|
if (video && !video.Error) {
|
||||||
|
result.push(video);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Video;
|
|
@ -52,8 +52,9 @@
|
||||||
import {Photo, MediaLive, MediaRaw, MediaVideo, MediaAnimated} from "model/photo";
|
import {Photo, MediaLive, MediaRaw, MediaVideo, MediaAnimated} from "model/photo";
|
||||||
import Album from "model/album";
|
import Album from "model/album";
|
||||||
import Thumb from "model/thumb";
|
import Thumb from "model/thumb";
|
||||||
import Event from "pubsub-js";
|
import Video from "model/video";
|
||||||
import Viewer from "common/viewer";
|
import Viewer from "common/viewer";
|
||||||
|
import Event from "pubsub-js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'PPageAlbumPhotos',
|
name: 'PPageAlbumPhotos',
|
||||||
|
@ -241,7 +242,8 @@ export default {
|
||||||
*/
|
*/
|
||||||
if (preferVideo && selected.Type === MediaLive || selected.Type === MediaVideo || selected.Type === MediaAnimated) {
|
if (preferVideo && selected.Type === MediaLive || selected.Type === MediaVideo || selected.Type === MediaAnimated) {
|
||||||
if (selected.isPlayable()) {
|
if (selected.isPlayable()) {
|
||||||
this.$viewer.play({video: selected, album: this.album});
|
const play = Video.fromPhotos(this.results, index);
|
||||||
|
this.$viewer.play({videos: play.videos, index: play.index, album: this.albums});
|
||||||
} else {
|
} else {
|
||||||
this.$viewer.show(Thumb.fromPhotos(this.results), index);
|
this.$viewer.show(Thumb.fromPhotos(this.results), index);
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,7 @@
|
||||||
<script>
|
<script>
|
||||||
import {MediaAnimated, MediaLive, MediaRaw, MediaVideo, Photo} from "model/photo";
|
import {MediaAnimated, MediaLive, MediaRaw, MediaVideo, Photo} from "model/photo";
|
||||||
import Thumb from "model/thumb";
|
import Thumb from "model/thumb";
|
||||||
|
import Video from "model/video";
|
||||||
import Viewer from "common/viewer";
|
import Viewer from "common/viewer";
|
||||||
import Event from "pubsub-js";
|
import Event from "pubsub-js";
|
||||||
|
|
||||||
|
@ -323,7 +324,7 @@ export default {
|
||||||
*/
|
*/
|
||||||
if (preferVideo && selected.Type === MediaLive || selected.Type === MediaVideo || selected.Type === MediaAnimated) {
|
if (preferVideo && selected.Type === MediaLive || selected.Type === MediaVideo || selected.Type === MediaAnimated) {
|
||||||
if (selected.isPlayable()) {
|
if (selected.isPlayable()) {
|
||||||
this.$viewer.play({video: selected});
|
this.$viewer.play(Video.fromPhotos(this.results, index));
|
||||||
} else {
|
} else {
|
||||||
this.$viewer.show(Thumb.fromPhotos(this.results), index);
|
this.$viewer.show(Thumb.fromPhotos(this.results), index);
|
||||||
}
|
}
|
||||||
|
|
|
@ -310,6 +310,8 @@ Mock.onDelete("api/v1/link/5").reply(200, "delete success", mockHeaders);
|
||||||
|
|
||||||
Mock.onPost("api/v1/photos/55/like").reply(200, { status: "ok" }, mockHeaders);
|
Mock.onPost("api/v1/photos/55/like").reply(200, { status: "ok" }, mockHeaders);
|
||||||
Mock.onDelete("api/v1/photos/55/like").reply(200, { status: "ok" }, mockHeaders);
|
Mock.onDelete("api/v1/photos/55/like").reply(200, { status: "ok" }, mockHeaders);
|
||||||
|
Mock.onPost("api/v1/photos/prqjmzr1jlmr4mpb/like").reply(200, { status: "ok" }, mockHeaders);
|
||||||
|
Mock.onDelete("api/v1/photos/prqjmzr1jlmr4mpb/like").reply(200, { status: "ok" }, mockHeaders);
|
||||||
Mock.onGet("api/v1/albums/5").reply(200, { UID: "5" }, mockHeaders);
|
Mock.onGet("api/v1/albums/5").reply(200, { UID: "5" }, mockHeaders);
|
||||||
Mock.onPut("api/v1/photos/5").reply(200, { UID: "5" }, mockHeaders);
|
Mock.onPut("api/v1/photos/5").reply(200, { UID: "5" }, mockHeaders);
|
||||||
Mock.onDelete("api/v1/photos/abc123/like").reply(200, { status: "ok" }, mockHeaders);
|
Mock.onDelete("api/v1/photos/abc123/like").reply(200, { status: "ok" }, mockHeaders);
|
||||||
|
|
|
@ -120,7 +120,7 @@ describe("model/file", () => {
|
||||||
Name: "1/2/IMG123.jpg",
|
Name: "1/2/IMG123.jpg",
|
||||||
};
|
};
|
||||||
const file = new File(values);
|
const file = new File(values);
|
||||||
assert.equal(file.getDownloadUrl("abc"), "/api/v1/dl/54ghtfd?t=2lbh9x09");
|
assert.equal(file.downloadUrl("abc"), "/api/v1/dl/54ghtfd?t=2lbh9x09");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not download as hash is missing", () => {
|
it("should not download as hash is missing", () => {
|
||||||
|
|
|
@ -120,7 +120,7 @@ describe("model/photo", () => {
|
||||||
it("should get photo download url", () => {
|
it("should get photo download url", () => {
|
||||||
const values = { ID: 5, Title: "Crazy Cat", Hash: 345982 };
|
const values = { ID: 5, Title: "Crazy Cat", Hash: 345982 };
|
||||||
const photo = new Photo(values);
|
const photo = new Photo(values);
|
||||||
const result = photo.getDownloadUrl();
|
const result = photo.downloadUrl();
|
||||||
assert.equal(result, "/api/v1/dl/345982?t=2lbh9x09");
|
assert.equal(result, "/api/v1/dl/345982?t=2lbh9x09");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -636,11 +636,17 @@ describe("model/photo", () => {
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
const photo3 = new Photo(values3);
|
const photo3 = new Photo(values3);
|
||||||
const result = photo3.videoParams();
|
|
||||||
assert.equal(result.height, "463");
|
const video = photo3.video();
|
||||||
assert.equal(result.width, "695");
|
assert.equal(video.Height, 600);
|
||||||
assert.equal(result.loop, false);
|
assert.equal(video.Width, 900);
|
||||||
assert.equal(result.uri, "/api/v1/videos/1xxbgdt55/public/avc");
|
assert.equal(video.loop(), false);
|
||||||
|
assert.equal(video.url(), "/api/v1/videos/1xxbgdt55/public/avc");
|
||||||
|
|
||||||
|
const playerSize = video.playerSize();
|
||||||
|
assert.equal(playerSize.height, 470);
|
||||||
|
assert.equal(playerSize.width, 705);
|
||||||
|
|
||||||
const values = {
|
const values = {
|
||||||
ID: 11,
|
ID: 11,
|
||||||
UID: "ABC127",
|
UID: "ABC127",
|
||||||
|
@ -667,12 +673,17 @@ describe("model/photo", () => {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const photo = new Photo(values);
|
const photo = new Photo(values);
|
||||||
const result2 = photo.videoParams();
|
const video2 = photo.video();
|
||||||
assert.equal(result2.height, "440");
|
assert.equal(video2.Height, 5000);
|
||||||
assert.equal(result2.width, "440");
|
assert.equal(video2.Width, 5000);
|
||||||
assert.equal(result2.loop, false);
|
assert.equal(video2.loop(), false);
|
||||||
assert.equal(result2.uri, "/api/v1/videos/1xxbgdt55/public/avc");
|
assert.equal(video2.url(), "/api/v1/videos/1xxbgdt55/public/avc");
|
||||||
|
|
||||||
|
const playerSize2 = video2.playerSize();
|
||||||
|
assert.equal(playerSize2.height, 460);
|
||||||
|
assert.equal(playerSize2.width, 460);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return videofile", () => {
|
it("should return videofile", () => {
|
||||||
|
|
|
@ -66,8 +66,8 @@ describe("model/thumb", () => {
|
||||||
assert.equal(thumb.Favorite, true);
|
assert.equal(thumb.Favorite, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return thumb not found", () => {
|
it("should return not placeholder", () => {
|
||||||
const result = Thumb.thumbNotFound();
|
const result = Thumb.notFound();
|
||||||
assert.equal(result.UID, "");
|
assert.equal(result.UID, "");
|
||||||
assert.equal(result.Favorite, false);
|
assert.equal(result.Favorite, false);
|
||||||
});
|
});
|
||||||
|
|
1011
frontend/tests/unit/model/video_test.js
Normal file
1011
frontend/tests/unit/model/video_test.js
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue