Skip to content

Commit

Permalink
feat: Fallback to manually loading media on error
Browse files Browse the repository at this point in the history
This is needed for E2EE files that will error when loading them directly from the HTML element's src attribute.

Signed-off-by: Louis Chemineau <[email protected]>
  • Loading branch information
artonge committed Jan 16, 2025
1 parent 236ff30 commit 00eb536
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 8 deletions.
37 changes: 34 additions & 3 deletions src/components/Audios.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@
<template>
<!-- Plyr currently replaces the parent. Wrapping to prevent this
https://github.com/redxtech/vue-plyr/issues/259 -->
<div v-if="src">
<div v-if="url">
<VuePlyr ref="plyr"
:options="options">
<audio ref="audio"
:autoplay="active"
:src="src"
:src="url"
preload="metadata"
@error.capture.prevent.stop.once="onFail"
@ended="donePlaying"
@canplay="doneLoading">

Expand All @@ -28,20 +29,32 @@
</div>
</template>

<script>
<script lang='ts'>
import Vue from 'vue'
import AsyncComputed from 'vue-async-computed'
// eslint-disable-next-line n/no-missing-import
import '@skjnldsv/vue-plyr/dist/vue-plyr.css'

import logger from '../services/logger.js'
import { preloadMedia } from '../services/mediaPreloader'

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

Vue.use(AsyncComputed)

export default {
name: 'Audios',

components: {
VuePlyr,
},

data() {
return {
fallback: false,
}
},

computed: {
player() {
return this.$refs.plyr.player
Expand All @@ -57,6 +70,16 @@ export default {
},
},

asyncComputed: {
async url(): Promise<string> {
if (this.fallback) {
return preloadMedia(this.filename)
} else {
return this.src
}
},
},

watch: {
active(val, old) {
// the item was hidden before and is now the current view
Expand Down Expand Up @@ -94,6 +117,14 @@ export default {
this.$refs.audio.autoplay = false
this.$refs.audio.load()
},

// Fallback to the original image if not already done
onFail() {
if (!this.fallback) {
console.error(`Loading of file ${this.filename} failed, falling back to fetching it by hand`)
this.fallback = true
}
},
},
}
</script>
Expand Down
10 changes: 8 additions & 2 deletions src/components/Images.vue
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
</div>
</template>

<script>
<script lang="ts">
import Vue from 'vue'
import AsyncComputed from 'vue-async-computed'
import PlayCircleOutline from 'vue-material-design-icons/PlayCircleOutline.vue'
Expand All @@ -85,6 +85,7 @@ import { NcLoadingIcon } from '@nextcloud/vue'
import ImageEditor from './ImageEditor.vue'
import { findLivePhotoPeerFromFileId } from '../utils/livePhotoUtils'
import { getDavPath } from '../utils/fileUtils'
import { preloadMedia } from '../services/mediaPreloader'

Vue.use(AsyncComputed)

Expand Down Expand Up @@ -181,7 +182,12 @@ export default {
// If there is no preview and we have a direct source
// load it instead
if (this.source && !this.hasPreview && !this.previewUrl) {
return this.source
// If loading the source failed once, let's try fetching it by had
if (this.fallback) {
return preloadMedia(this.filename)
} else {
return this.source
}
}

// If loading the preview failed once, let's load the original file
Expand Down
33 changes: 30 additions & 3 deletions src/components/Videos.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<template>
<!-- Plyr currently replaces the parent. Wrapping to prevent this
https://github.com/redxtech/vue-plyr/issues/259 -->
<div v-if="src">
<div v-if="url">
<VuePlyr ref="plyr"
:options="options"
:style="{
Expand All @@ -17,8 +17,9 @@
:autoplay="active ? true : null"
:playsinline="true"
:poster="livePhotoPath"
:src="src"
:src="url"
preload="metadata"
@error.capture.prevent.stop.once="onFail"
@ended="donePlaying"
@canplay="doneLoading"
@loadedmetadata="onLoadedMetadata">
Expand All @@ -35,18 +36,25 @@
</div>
</template>

<script>
<script lang='ts'>
// eslint-disable-next-line n/no-missing-import
import Vue from 'vue'
import AsyncComputed from 'vue-async-computed'
import '@skjnldsv/vue-plyr/dist/vue-plyr.css'

import { imagePath } from '@nextcloud/router'

import logger from '../services/logger.js'
import { findLivePhotoPeerFromName } from '../utils/livePhotoUtils'
import { getPreviewIfAny } from '../utils/previewUtils'
import { preloadMedia } from '../services/mediaPreloader.js'

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

const blankVideo = imagePath('viewer', 'blank.mp4')

Vue.use(AsyncComputed)

export default {
name: 'Videos',

Expand All @@ -56,6 +64,7 @@ export default {
data() {
return {
isFullscreenButtonVisible: false,
fallback: false,
}
},

Expand Down Expand Up @@ -86,6 +95,16 @@ export default {
},
},

asyncComputed: {
async url(): Promise<string> {
if (this.fallback) {
return preloadMedia(this.filename)
} else {
return this.src
}
},
},

watch: {
active(val, old) {
// the item was hidden before and is now the current view
Expand Down Expand Up @@ -155,6 +174,14 @@ export default {
this.player.stop()
}
},

// Fallback to the original image if not already done
onFail() {
if (!this.fallback) {
console.error(`Loading of file ${this.filename} failed, falling back to fetching it by hand`)
this.fallback = true
}
},
},
}
</script>
Expand Down
19 changes: 19 additions & 0 deletions src/services/mediaPreloader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

/* eslint-disable jsdoc/require-jsdoc */

import type { ResponseDataDetailed, WebDAVClient } from 'webdav'

import { getClient, getRootPath } from '@nextcloud/files/dav'

// Manually load a WebDAV media from its filename, then expose the received Blob as an object URL.
// This is needed for E2EE files that will error when loading them directly from the HTML element's src attribute.
// Can be removed if we ever move the E2EE proxy to a service worker.
export async function preloadMedia(filename: string): Promise<string> {
const client = getClient() as WebDAVClient
const response = await client.getFileContents(`${getRootPath}${filename}`, { details: true }) as ResponseDataDetailed<ArrayBuffer>
return URL.createObjectURL(new Blob([response.data], { type: response.headers['content-type'] }))
}

0 comments on commit 00eb536

Please sign in to comment.