Skip to content

Commit

Permalink
Add live photo support base on files metadata
Browse files Browse the repository at this point in the history
Signed-off-by: Louis Chemineau <[email protected]>
  • Loading branch information
artonge committed Dec 20, 2023
1 parent 464ca47 commit 8538515
Show file tree
Hide file tree
Showing 18 changed files with 338 additions and 127 deletions.
4 changes: 2 additions & 2 deletions js/viewer-components.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion js/viewer-components.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions js/viewer-main.js

Large diffs are not rendered by default.

22 changes: 22 additions & 0 deletions js/viewer-main.js.LICENSE.txt
Original file line number Diff line number Diff line change
Expand Up @@ -310,3 +310,25 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

/**
* @copyright Copyright (c) 2023 Louis Chmn <[email protected]>
*
* @author Louis Chmn <[email protected]>
*
* @license AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
2 changes: 1 addition & 1 deletion js/viewer-main.js.map

Large diffs are not rendered by default.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

180 changes: 145 additions & 35 deletions src/components/Images.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,34 +21,77 @@
-->

<template>
<ImageEditor v-if="editing"
:mime="mime"
:src="src"
:fileid="fileid"
@close="onClose" />

<img v-else-if="data !== null"
:alt="alt"
:class="{
dragging,
loaded,
zoomed: zoomRatio !== 1
}"
:src="data"
:style="imgStyle"
@error.capture.prevent.stop.once="onFail"
@load="updateImgSize"
@wheel="updateZoom"
@dblclick.prevent="onDblclick"
@mousedown.prevent="dragStart">
<div class="image_container">
<ImageEditor v-if="editing"
:mime="mime"
:src="src"
:fileid="fileid"
@close="onClose" />

<img v-else-if="data !== null && !livePhoto"
ref="image"
:alt="alt"
:class="{
dragging,
loaded,
zoomed: zoomRatio !== 1
}"
:src="data"
:style="imgStyle"
@error.capture.prevent.stop.once="onFail"
@load="updateImgSize"
@wheel="updateZoom"
@dblclick.prevent="onDblclick"
@mousedown.prevent="dragStart">

<video v-else-if="data !== null && livePhoto"
ref="video"
:class="{
dragging,
loaded,
zoomed: zoomRatio !== 1
}"
:style="imgStyle"
:playsinline="true"
:poster="data"
:src="livePhotoSrc"
preload="metadata"
@canplaythrough="doneLoadingLivePhoto"
@loadedmetadata="updateImgSize"
@wheel="updateZoom"
@error.capture.prevent.stop.once="onFail"
@dblclick.prevent="onDblclick"
@mousedown.prevent="dragStart"
@ended="stopLivePhoto" />
<button v-if="livePhoto"
class="live-photo_play_button"
:style="{left: `calc(50% - ${width/2}px)`}"
:disabled="!livePhotoCanBePlayed"
:aria-description="t('viewer', 'Play the live photo')"
@pointerenter="playLivePhoto"
@focus="playLivePhoto"
@pointerleave="stopLivePhoto"
@blur="stopLivePhoto">
<PlayCircleOutline v-if="livePhotoCanBePlayed" />
<NcLoadingIcon v-else />
{{ t('viewer', 'LIVE') }}
</button>
</div>
</template>

<script>
import axios from '@nextcloud/axios'
import Vue from 'vue'
import AsyncComputed from 'vue-async-computed'
import ImageEditor from './ImageEditor.vue'
import PlayCircleOutline from 'vue-material-design-icons/PlayCircleOutline.vue'

import axios from '@nextcloud/axios'
import { basename } from '@nextcloud/paths'
import { translate } from '@nextcloud/l10n'
import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'

import ImageEditor from './ImageEditor.vue'
import { livePhoto } from '../utils/livePhotoUtils'
import { getDavPath } from '../utils/fileUtils'

Vue.use(AsyncComputed)

Expand All @@ -57,6 +100,8 @@ export default {

components: {
ImageEditor,
PlayCircleOutline,
NcLoadingIcon,
},

props: {
Expand All @@ -76,6 +121,7 @@ export default {
shiftY: 0,
zoomRatio: 1,
fallback: false,
livePhotoCanBePlayed: false,
}
},

Expand Down Expand Up @@ -103,6 +149,19 @@ export default {
width: this.zoomWidth + 'px',
}
},
livePhoto() {
return livePhoto(this, this.fileList)
},
livePhotoSrc() {
return this.livePhoto.source ?? this.livePhotoDavPath
},
/** @return {string|null} */
livePhotoDavPath() {
return getDavPath({
filename: this.livePhoto.filename,
basename: this.livePhoto.basename,
})
},
},

asyncComputed: {
Expand Down Expand Up @@ -147,8 +206,13 @@ export default {
methods: {
// Updates the dimensions of the modal
updateImgSize() {
this.naturalHeight = this.$el.naturalHeight
this.naturalWidth = this.$el.naturalWidth
if (this.$refs.image) {
this.naturalHeight = this.$refs.image.naturalHeight
this.naturalWidth = this.$refs.image.naturalWidth
} else if (this.$refs.video) {
this.naturalHeight = this.$refs.video.getBoundingClientRect().height
this.naturalWidth = this.$refs.video.getBoundingClientRect().width
}

this.updateHeightWidth()
this.doneLoading()
Expand All @@ -157,7 +221,7 @@ export default {
/**
* Manually retrieve the path and return its base64
*
* @return {string}
* @return {Promise<string>}
*/
async getBase64FromImage() {
const file = await axios.get(this.src)
Expand All @@ -167,8 +231,8 @@ export default {
/**
* Handle zooming
*
* @param {Event} event the scroll event
* @return {null}
* @param {WheelEvent} event the scroll event
* @return {void}
*/
updateZoom(event) {
if (!this.canZoom) {
Expand All @@ -179,8 +243,9 @@ export default {
event.preventDefault()

// scrolling position relative to the image
const scrollX = event.clientX - this.$el.x - (this.width * this.zoomRatio / 2)
const scrollY = event.clientY - this.$el.y - (this.height * this.zoomRatio / 2)
const element = this.$refs.image ?? this.$refs.video
const scrollX = event.clientX - element.x - (this.width * this.zoomRatio / 2)
const scrollY = event.clientY - element.y - (this.height * this.zoomRatio / 2)
const scrollPercX = scrollX / (this.width * this.zoomRatio)
const scrollPercY = scrollY / (this.height * this.zoomRatio)
const isZoomIn = event.deltaY < 0
Expand Down Expand Up @@ -216,24 +281,32 @@ export default {
/**
* Dragging handlers
*
* @param {Event} event the event
* @param {DragEvent} event the event
*/
dragStart(event) {
const { pageX, pageY } = event

this.dragX = pageX
this.dragY = pageY
this.dragging = true
this.$el.onmouseup = this.dragEnd
this.$el.onmousemove = this.dragHandler
const element = this.$refs.image ?? this.$refs.video
element.onmouseup = this.dragEnd
element.onmousemove = this.dragHandler
},
/**
* @param {DragEvent} event the event
*/
dragEnd(event) {
event.preventDefault()

this.dragging = false
this.$el.onmouseup = null
this.$el.onmousemove = null
const element = this.$refs.image ?? this.$refs.video
element.onmouseup = null
element.onmousemove = null
},
/**
* @param {DragEvent} event the event
*/
dragHandler(event) {
event.preventDefault()
const { pageX, pageY } = event
Expand Down Expand Up @@ -269,6 +342,22 @@ export default {
this.fallback = true
}
},
doneLoadingLivePhoto() {
this.livePhotoCanBePlayed = true
this.doneLoading()
},
playLivePhoto() {
/** @type {HTMLVideoElement} */
const video = this.$refs.video
video.play()
},
stopLivePhoto() {
/** @type {HTMLVideoElement} */
const video = this.$refs.video
video.load()
},

t: translate,
},
}
</script>
Expand All @@ -277,7 +366,14 @@ export default {
$checkered-size: 8px;
$checkered-color: #efefef;

img {
.image_container {
display: flex;
align-items: center;
height: 100%;
justify-content: center;
}

img, video {
max-width: 100%;
max-height: 100%;
align-self: center;
Expand Down Expand Up @@ -312,4 +408,18 @@ img {
cursor: move;
}
}

.live-photo_play_button {
position: absolute;
top: 0;
// left: is set dynamically on the element itself
margin: 16px !important;
display: flex;
align-items: center;
border: none;
gap: 4px;
border-radius: var(--border-radius);
padding: 4px 8px;
background-color: var(--color-main-background-blur);
}
</style>
24 changes: 11 additions & 13 deletions src/components/Videos.vue
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,13 @@
<script>
// eslint-disable-next-line n/no-missing-import
import '@skjnldsv/vue-plyr/dist/vue-plyr.css'
import logger from '../services/logger.js'
import { imagePath } from '@nextcloud/router'
import logger from '../services/logger.js'
import { livePhoto } from '../utils/livePhotoUtils'
import { getPreviewIfAny } from '../utils/previewUtils'

const VuePlyr = () => import(/* webpackChunkName: 'plyr' */'@skjnldsv/vue-plyr')

const liveExt = ['jpg', 'jpeg', 'png']
const liveExtRegex = new RegExp(`\\.(${liveExt.join('|')})$`, 'i')
const blankVideo = imagePath('viewer', 'blank.mp4')

export default {
Expand All @@ -78,16 +78,14 @@ export default {
},

computed: {
livePhoto() {
return this.fileList.find(file => {
// if same filename and extension is allowed
return file.filename !== this.filename
&& file.basename.startsWith(this.name)
&& liveExtRegex.test(file.basename)
})
},
livePhotoPath() {
return this.livePhoto && this.getPreviewIfAny(this.livePhoto)
const peerFile = livePhoto(this, this.fileList)

if (peerFile === undefined) {
return undefined
}

return getPreviewIfAny(peerFile)
},
player() {
return this.$refs.plyr.player
Expand All @@ -101,7 +99,7 @@ export default {
loadSprite: false,
fullscreen: {
iosNative: true,
}
},
}
},
},
Expand Down
5 changes: 5 additions & 0 deletions src/mixins/Mime.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ export default {
type: Boolean,
default: false,
},
// The file id of the peer live photo file
metadataFilesLivePhoto: {
type: Number,
default: undefined,
},
},

data() {
Expand Down
Loading

0 comments on commit 8538515

Please sign in to comment.