Refactor video download logic
This commit is contained in:
parent
e9239ec1b4
commit
36dfb7849d
|
@ -32,6 +32,10 @@ export default Vue.extend({
|
|||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
returnIndex: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
dropdownPositionX: {
|
||||
type: String,
|
||||
default: 'center'
|
||||
|
@ -47,11 +51,6 @@ export default Vue.extend({
|
|||
dropdownValues: {
|
||||
type: Array,
|
||||
default: () => { return [] }
|
||||
},
|
||||
relatedVideoTitle: {
|
||||
type: String,
|
||||
default: () => { return '' },
|
||||
require: false
|
||||
}
|
||||
},
|
||||
data: function () {
|
||||
|
@ -60,18 +59,6 @@ export default Vue.extend({
|
|||
id: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
filesExtensions: function() {
|
||||
const regex = /\/(\w*)/i
|
||||
return this.dropdownNames.slice().map((el) => {
|
||||
const group = el.match(regex)
|
||||
if (group === null || group.length === 0) {
|
||||
return ''
|
||||
}
|
||||
return group[1]
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted: function () {
|
||||
this.id = `iconButton${this._uid}`
|
||||
},
|
||||
|
@ -128,16 +115,12 @@ export default Vue.extend({
|
|||
},
|
||||
|
||||
handleDropdownClick: function (index) {
|
||||
if (this.relatedVideoTitle !== '') {
|
||||
this.$emit('click', {
|
||||
url: this.dropdownValues[index],
|
||||
title: this.relatedVideoTitle,
|
||||
extension: this.filesExtensions[index],
|
||||
folderPath: this.$store.getters.getDownloadFolderPath
|
||||
})
|
||||
if (this.returnIndex) {
|
||||
this.$emit('click', index)
|
||||
} else {
|
||||
this.$emit('click', this.dropdownValues[index])
|
||||
}
|
||||
|
||||
this.focusOut()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,13 +25,7 @@ export default Vue.extend({
|
|||
|
||||
toast.isOpen = false
|
||||
},
|
||||
open: function (message, action, time, translate = false, formatArgs = []) {
|
||||
if (translate) {
|
||||
message = this.$t(message)
|
||||
for (const arg of formatArgs) {
|
||||
message = message.replace('$', arg)
|
||||
}
|
||||
}
|
||||
open: function (message, action, time) {
|
||||
const toast = { message: message, action: action || (() => { }), isOpen: false, timeout: null }
|
||||
toast.timeout = setTimeout(this.close, time || 3000, toast)
|
||||
setImmediate(() => { toast.isOpen = true })
|
||||
|
|
|
@ -408,6 +408,27 @@ export default Vue.extend({
|
|||
}
|
||||
},
|
||||
|
||||
handleDownload: function (index) {
|
||||
const url = this.downloadLinkValues[index]
|
||||
const linkName = this.downloadLinkNames[index]
|
||||
const extension = this.grabExtensionFromUrl(linkName)
|
||||
|
||||
this.downloadMedia({
|
||||
url: url,
|
||||
title: this.title,
|
||||
extension: extension
|
||||
})
|
||||
},
|
||||
|
||||
grabExtensionFromUrl: function (url) {
|
||||
const regex = /\/(\w*)/i
|
||||
const group = url.match(regex)
|
||||
if (group.length === 0) {
|
||||
return ''
|
||||
}
|
||||
return group[1]
|
||||
},
|
||||
|
||||
addToPlaylist: function () {
|
||||
const videoData = {
|
||||
videoId: this.id,
|
||||
|
|
|
@ -99,10 +99,10 @@
|
|||
class="option"
|
||||
theme="secondary"
|
||||
icon="download"
|
||||
:return-index="true"
|
||||
:dropdown-names="downloadLinkNames"
|
||||
:dropdown-values="downloadLinkValues"
|
||||
:related-video-title="title"
|
||||
@click="downloadMedia"
|
||||
@click="handleDownload"
|
||||
/>
|
||||
<ft-icon-button
|
||||
v-if="!isUpcoming"
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import IsEqual from 'lodash.isequal'
|
||||
import FtToastEvents from '../../components/ft-toast/ft-toast-events'
|
||||
import fs from 'fs'
|
||||
import i18n from '../../i18n/index'
|
||||
|
||||
import { IpcChannels } from '../../../constants'
|
||||
import { ipcRenderer } from 'electron'
|
||||
|
@ -176,113 +177,92 @@ const actions = {
|
|||
}
|
||||
},
|
||||
|
||||
async downloadMedia({ rootState, dispatch }, { url, title, extension, folderPath, fallingBackPath }) {
|
||||
async downloadMedia({ rootState, dispatch }, { url, title, extension, fallingBackPath }) {
|
||||
const fileName = `${title}.${extension}`
|
||||
const usingElectron = rootState.settings.usingElectron
|
||||
const askFolderPath = folderPath === ''
|
||||
let filePathSelected
|
||||
const successMsg = 'Downloading has completed'
|
||||
const locale = i18n._vm.locale
|
||||
const translations = i18n._vm.messages[locale]
|
||||
const startMessage = translations['Starting download'].replace('$', title)
|
||||
const completedMessage = translations['Downloading has completed'].replace('$', title)
|
||||
const errorMessage = translations['Downloading failed'].replace('$', title)
|
||||
let folderPath = rootState.settings.downloadFolderPath
|
||||
|
||||
if (askFolderPath && usingElectron) {
|
||||
const resp = await ipcRenderer.invoke(
|
||||
IpcChannels.SHOW_SAVE_DIALOG,
|
||||
{ defaultPath: `${title}.${extension}` }
|
||||
)
|
||||
filePathSelected = resp.filePath
|
||||
if (!usingElectron) {
|
||||
// Add logic here in the future
|
||||
return
|
||||
}
|
||||
|
||||
if (fallingBackPath !== undefined) {
|
||||
dispatch('showToast', {
|
||||
message: 'Download folder does not exist',
|
||||
translate: true,
|
||||
formatArgs: [fallingBackPath]
|
||||
})
|
||||
if (folderPath === '') {
|
||||
const options = {
|
||||
defaultPath: fileName,
|
||||
filters: [
|
||||
{
|
||||
extensions: [extension]
|
||||
}
|
||||
]
|
||||
}
|
||||
const response = await dispatch('showSaveDialog', options)
|
||||
|
||||
if (response.canceled || response.filePath === '') {
|
||||
// User canceled the save dialog
|
||||
return
|
||||
}
|
||||
|
||||
folderPath = response.filePath
|
||||
}
|
||||
|
||||
dispatch('showToast', {
|
||||
message: 'Starting download', translate: true, formatArgs: [title]
|
||||
message: startMessage
|
||||
})
|
||||
|
||||
const response = await fetch(url).catch((error) => {
|
||||
console.log(error)
|
||||
dispatch('showToast', {
|
||||
message: errorMessage
|
||||
})
|
||||
})
|
||||
|
||||
const response = await fetch(url)
|
||||
// mechanism to show the download progress reference https://javascript.info/fetch-progress
|
||||
const reader = response.body.getReader()
|
||||
|
||||
const contentLength = response.headers.get('Content-Length')
|
||||
|
||||
let receivedLength = 0
|
||||
const chunks = []
|
||||
// manage frequency notifications to the user
|
||||
const intervalPercentageNotification = 0.2
|
||||
let lastPercentageNotification = 0
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read()
|
||||
const handleError = (err) => {
|
||||
console.log(err)
|
||||
dispatch('showToast', {
|
||||
message: errorMessage
|
||||
})
|
||||
}
|
||||
|
||||
const processText = async ({ done, value }) => {
|
||||
if (done) {
|
||||
break
|
||||
return
|
||||
}
|
||||
|
||||
chunks.push(value)
|
||||
receivedLength += value.length
|
||||
// Can be used in the future to determine download percentage
|
||||
const percentage = receivedLength / contentLength
|
||||
if (percentage > (lastPercentageNotification + intervalPercentageNotification)) {
|
||||
// mechanism kept for an upcoming download page
|
||||
lastPercentageNotification = percentage
|
||||
}
|
||||
await reader.read().then(processText).catch(handleError)
|
||||
}
|
||||
|
||||
const chunksAll = new Uint8Array(receivedLength)
|
||||
let position = 0
|
||||
for (const chunk of chunks) {
|
||||
chunksAll.set(chunk, position)
|
||||
position += chunk.length
|
||||
}
|
||||
await reader.read().then(processText).catch(handleError)
|
||||
|
||||
// write the file into the hardrive
|
||||
if (!response.ok) {
|
||||
console.error(`"Unable to download ${title}, return status code ${response.status}`)
|
||||
dispatch('showToast', {
|
||||
message: 'Downloading failed', translate: true, formatArgs: [title, response.status]
|
||||
})
|
||||
return
|
||||
}
|
||||
const blobFile = new Blob(chunks)
|
||||
const buffer = await blobFile.arrayBuffer()
|
||||
|
||||
if (usingElectron && !askFolderPath) {
|
||||
fs.writeFile(`${folderPath}/${title}.${extension}`, new DataView(buffer), (err) => {
|
||||
if (err) {
|
||||
console.error(err)
|
||||
dispatch('updateDownloadFolderPath', '')
|
||||
dispatch('downloadMedia', { url: url, title: title, extension: extension, folderPath: '', fallingBackPath: folderPath })
|
||||
} else {
|
||||
dispatch('showToast', {
|
||||
message: successMsg, translate: true, formatArgs: [title]
|
||||
})
|
||||
}
|
||||
})
|
||||
} else if (usingElectron) {
|
||||
fs.writeFile(filePathSelected, new DataView(buffer), (err) => {
|
||||
if (err) {
|
||||
console.error(err)
|
||||
if (filePathSelected === '') {
|
||||
dispatch('showToast', {
|
||||
message: 'Downloading canceled',
|
||||
translate: true
|
||||
})
|
||||
} else {
|
||||
dispatch('showToast', {
|
||||
message: err
|
||||
})
|
||||
}
|
||||
} else {
|
||||
dispatch('showToast', {
|
||||
message: successMsg, translate: true, formatArgs: [title]
|
||||
})
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// Web placeholder
|
||||
}
|
||||
fs.writeFile(folderPath, new DataView(buffer), (err) => {
|
||||
if (err) {
|
||||
console.error(err)
|
||||
dispatch('showToast', {
|
||||
message: errorMessage
|
||||
})
|
||||
} else {
|
||||
dispatch('showToast', {
|
||||
message: completedMessage
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
async getSystemLocale (context) {
|
||||
|
@ -790,9 +770,7 @@ const actions = {
|
|||
},
|
||||
|
||||
showToast (_, payload) {
|
||||
const formatArgs = 'formatArgs' in payload ? payload.formatArgs : []
|
||||
const translate = 'translate' in payload ? payload.translate : false
|
||||
FtToastEvents.$emit('toast-open', payload.message, payload.action, payload.time, translate, formatArgs)
|
||||
FtToastEvents.$emit('toast-open', payload.message, payload.action, payload.time)
|
||||
},
|
||||
|
||||
showExternalPlayerUnsupportedActionToast: function ({ dispatch }, payload) {
|
||||
|
|
|
@ -713,11 +713,9 @@ Default Invidious instance has been cleared: Default Invidious instance has been
|
|||
'The playlist has ended. Enable loop to continue playing': 'The playlist has ended. Enable
|
||||
loop to continue playing'
|
||||
External link opening has been disabled in the general settings: 'External link opening has been disabled in the general settings'
|
||||
Downloading has completed: 'Downloading "$" has completed'
|
||||
Starting download: 'Downloading "$" has started'
|
||||
Downloading failed: 'Unable to download "$", return http request status code $'
|
||||
Downloading canceled: The dowload is canceled by the user
|
||||
Download folder does not exist: The download directory "$" doesn't exist. Falling back to "ask folder" mode.
|
||||
Downloading has completed: '"$" has finished downloading'
|
||||
Starting download: 'Starting download of "$"'
|
||||
Downloading failed: 'There was an issue downloading "$"'
|
||||
|
||||
Yes: Yes
|
||||
No: No
|
||||
|
|
Loading…
Reference in New Issue