mirror of https://github.com/FreeTubeApp/FreeTube
add piped settings + fallback when not supported
This commit is contained in:
parent
17c7510f90
commit
3633a86ac3
|
@ -0,0 +1,7 @@
|
|||
const fs = require('fs/promises')
|
||||
const url = 'https://piped-instances.kavin.rocks/'
|
||||
|
||||
fetch(url).then(e => e.json()).then(res => {
|
||||
const data = res.map(e => e.api_url)
|
||||
fs.writeFile('././static/piped-instances.json', JSON.stringify(data))
|
||||
})
|
|
@ -31,6 +31,7 @@
|
|||
"dev": "run-s rebuild:electron dev-runner",
|
||||
"dev:web": "node _scripts/dev-runner.js --web",
|
||||
"dev-runner": "node _scripts/dev-runner.js",
|
||||
"refetch-piped-instances": "node _scripts/getPipedInstances",
|
||||
"lint-all": "run-p lint lint-json lint-style",
|
||||
"lint-fix": "eslint --fix --ext .js,.vue ./",
|
||||
"lint": "eslint --ext .js,.vue ./",
|
||||
|
|
|
@ -84,6 +84,9 @@ export default defineComponent({
|
|||
defaultInvidiousInstance: function () {
|
||||
return this.$store.getters.getDefaultInvidiousInstance
|
||||
},
|
||||
defaultPipedInstance: function () {
|
||||
return this.$store.getters.getDefaultPipedInstance
|
||||
},
|
||||
|
||||
baseTheme: function () {
|
||||
return this.$store.getters.getBaseTheme
|
||||
|
@ -140,6 +143,11 @@ export default defineComponent({
|
|||
await this.setRandomCurrentInvidiousInstance()
|
||||
}
|
||||
|
||||
await this.fetchPipedInstances()
|
||||
if (this.defaultPipedInstance === '') {
|
||||
await this.setRandomCurrentPipedInstance()
|
||||
}
|
||||
|
||||
this.grabAllProfiles(this.$t('Profile.All Channels')).then(async () => {
|
||||
this.grabHistory()
|
||||
this.grabAllPlaylists()
|
||||
|
@ -508,6 +516,8 @@ export default defineComponent({
|
|||
'getExternalPlayerCmdArgumentsData',
|
||||
'fetchInvidiousInstances',
|
||||
'setRandomCurrentInvidiousInstance',
|
||||
'fetchPipedInstances',
|
||||
'setRandomCurrentPipedInstance',
|
||||
'setupListenersToSyncWindows',
|
||||
'updateBaseTheme',
|
||||
'updateMainColor',
|
||||
|
|
|
@ -41,10 +41,14 @@ export default defineComponent({
|
|||
},
|
||||
computed: {
|
||||
backendPreference: function () {
|
||||
return this.$store.getters.getBackendPreference
|
||||
let preference = this.$store.getters.getBackendPreference
|
||||
if (preference === 'piped') {
|
||||
preference = this.$store.getters.getFallbackPreference
|
||||
}
|
||||
return preference
|
||||
},
|
||||
backendFallback: function () {
|
||||
return this.$store.getters.getBackendFallback
|
||||
return this.$store.getters.getBackendFallback && this.$store.getters.getBackendPreference !== 'piped'
|
||||
},
|
||||
profileList: function () {
|
||||
return this.$store.getters.getProfileList
|
||||
|
|
|
@ -8,8 +8,10 @@ import {
|
|||
openExternalLink,
|
||||
showToast,
|
||||
toLocalePublicationString,
|
||||
toDistractionFreeTitle
|
||||
toDistractionFreeTitle,
|
||||
isNullOrEmpty
|
||||
} from '../../helpers/utils'
|
||||
import { getPipedUrlInfo } from '../../helpers/api/piped'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'FtListVideo',
|
||||
|
@ -91,6 +93,10 @@ export default defineComponent({
|
|||
return this.$store.getters.getBackendPreference
|
||||
},
|
||||
|
||||
fallbackPreference: function () {
|
||||
return this.$store.getters.getFallbackPreference
|
||||
},
|
||||
|
||||
currentInvidiousInstance: function () {
|
||||
return this.$store.getters.getCurrentInvidiousInstance
|
||||
},
|
||||
|
@ -220,23 +226,44 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
thumbnail: function () {
|
||||
let baseUrl
|
||||
if (this.backendPreference === 'invidious') {
|
||||
baseUrl = this.currentInvidiousInstance
|
||||
} else {
|
||||
baseUrl = 'https://i.ytimg.com'
|
||||
let baseUrl = ''
|
||||
let baseData = ''
|
||||
let backendPreference = this.backendPreference
|
||||
if (backendPreference === 'piped') {
|
||||
if (this.data.thumbnail) {
|
||||
baseData = getPipedUrlInfo(this.data.thumbnail)
|
||||
baseUrl = baseData.baseUrl
|
||||
} else {
|
||||
backendPreference = this.fallbackPreference
|
||||
}
|
||||
}
|
||||
if (isNullOrEmpty(baseData)) {
|
||||
if (backendPreference === 'invidious') {
|
||||
baseUrl = this.currentInvidiousInstance
|
||||
} else {
|
||||
baseUrl = 'https://i.ytimg.com'
|
||||
}
|
||||
}
|
||||
|
||||
let imageUrl = ''
|
||||
|
||||
switch (this.thumbnailPreference) {
|
||||
case 'start':
|
||||
return `${baseUrl}/vi/${this.id}/mq1.jpg`
|
||||
imageUrl = `${baseUrl}/vi/${this.id}/mq1.jpg`
|
||||
break
|
||||
case 'middle':
|
||||
return `${baseUrl}/vi/${this.id}/mq2.jpg`
|
||||
imageUrl = `${baseUrl}/vi/${this.id}/mq2.jpg`
|
||||
break
|
||||
case 'end':
|
||||
return `${baseUrl}/vi/${this.id}/mq3.jpg`
|
||||
imageUrl = `${baseUrl}/vi/${this.id}/mq3.jpg`
|
||||
break
|
||||
default:
|
||||
return `${baseUrl}/vi/${this.id}/mqdefault.jpg`
|
||||
imageUrl = `${baseUrl}/vi/${this.id}/mqdefault.jpg`
|
||||
}
|
||||
if (!isNullOrEmpty(baseData)) {
|
||||
imageUrl += `?host=${baseData.host}`
|
||||
}
|
||||
return imageUrl
|
||||
},
|
||||
|
||||
hideVideoViews: function () {
|
||||
|
|
|
@ -24,7 +24,8 @@ export default defineComponent({
|
|||
return {
|
||||
backendValues: [
|
||||
'invidious',
|
||||
'local'
|
||||
'local',
|
||||
'piped'
|
||||
],
|
||||
defaultPageNames: [
|
||||
'Subscriptions',
|
||||
|
@ -58,6 +59,9 @@ export default defineComponent({
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
currentPipedInstance: function () {
|
||||
return this.$store.getters.getCurrentPipedInstance
|
||||
},
|
||||
currentInvidiousInstance: function () {
|
||||
return this.$store.getters.getCurrentInvidiousInstance
|
||||
},
|
||||
|
@ -67,6 +71,9 @@ export default defineComponent({
|
|||
backendFallback: function () {
|
||||
return this.$store.getters.getBackendFallback
|
||||
},
|
||||
fallbackPreference: function () {
|
||||
return this.$store.getters.getFallbackPreference
|
||||
},
|
||||
checkForUpdates: function () {
|
||||
return this.$store.getters.getCheckForUpdates
|
||||
},
|
||||
|
@ -103,6 +110,12 @@ export default defineComponent({
|
|||
defaultInvidiousInstance: function () {
|
||||
return this.$store.getters.getDefaultInvidiousInstance
|
||||
},
|
||||
pipedInstancesList: function () {
|
||||
return this.$store.getters.getPipedInstancesList
|
||||
},
|
||||
defaultPipedInstance: function () {
|
||||
return this.$store.getters.getDefaultPipedInstance
|
||||
},
|
||||
|
||||
localeOptions: function () {
|
||||
return [
|
||||
|
@ -121,7 +134,8 @@ export default defineComponent({
|
|||
backendNames: function () {
|
||||
return [
|
||||
this.$t('Settings.General Settings.Preferred API Backend.Invidious API'),
|
||||
this.$t('Settings.General Settings.Preferred API Backend.Local API')
|
||||
this.$t('Settings.General Settings.Preferred API Backend.Local API'),
|
||||
this.$t('Settings.General Settings.Preferred API Backend.Piped API')
|
||||
]
|
||||
},
|
||||
|
||||
|
@ -156,18 +170,27 @@ export default defineComponent({
|
|||
mounted: function () {
|
||||
this.setCurrentInvidiousInstanceBounce =
|
||||
debounce(this.setCurrentInvidiousInstance, 500)
|
||||
|
||||
this.setCurrentPipedInstanceBounce =
|
||||
debounce(this.setCurrentPipedInstance, 500)
|
||||
},
|
||||
beforeDestroy: function () {
|
||||
// FIXME: If we call an action from here, there's no guarantee it will finish
|
||||
// before the component is destroyed, which could bring up some problems
|
||||
// Since I can't see any way to await it (because lifecycle hooks must be
|
||||
// synchronous), unfortunately, we have to copy/paste the logic
|
||||
// from the `setRandomCurrentInvidiousInstance` action onto here
|
||||
if (this.currentInvidiousInstance === '') {
|
||||
// FIXME: If we call an action from here, there's no guarantee it will finish
|
||||
// before the component is destroyed, which could bring up some problems
|
||||
// Since I can't see any way to await it (because lifecycle hooks must be
|
||||
// synchronous), unfortunately, we have to copy/paste the logic
|
||||
// from the `setRandomCurrentInvidiousInstance` action onto here
|
||||
const instanceList = this.invidiousInstancesList
|
||||
const randomIndex = Math.floor(Math.random() * instanceList.length)
|
||||
this.setCurrentInvidiousInstance(instanceList[randomIndex])
|
||||
}
|
||||
|
||||
if (this.setCurrentPipedInstance === '') {
|
||||
const instanceList = this.pipedInstanceList
|
||||
const randomIndex = Math.floor(Math.random() * instanceList.length)
|
||||
this.setCurrentPipedInstance(instanceList[randomIndex])
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleInvidiousInstanceInput: function (input) {
|
||||
|
@ -175,6 +198,10 @@ export default defineComponent({
|
|||
this.setCurrentInvidiousInstanceBounce(instance)
|
||||
},
|
||||
|
||||
handlePipedInstanceInput: function (input) {
|
||||
this.setCurrentPipedInstanceBounce(input)
|
||||
},
|
||||
|
||||
handleSetDefaultInstanceClick: function () {
|
||||
const instance = this.currentInvidiousInstance
|
||||
this.updateDefaultInvidiousInstance(instance)
|
||||
|
@ -183,21 +210,52 @@ export default defineComponent({
|
|||
showToast(message)
|
||||
},
|
||||
|
||||
handleSetDefaultPipedInstanceClick: function () {
|
||||
const instance = this.currentPipedInstance
|
||||
this.updateDefaultPipedInstance(instance)
|
||||
|
||||
const message = this.$t('Default Piped instance has been set to {instance}', { instance })
|
||||
showToast(message)
|
||||
},
|
||||
|
||||
handleClearDefaultInstanceClick: function () {
|
||||
this.updateDefaultInvidiousInstance('')
|
||||
showToast(this.$t('Default Invidious instance has been cleared'))
|
||||
},
|
||||
|
||||
handleClearDefaultPipedInstanceClick: function () {
|
||||
this.updateDefaultPipedInstance('')
|
||||
showToast(this.$t('Default Piped instance has been cleared'))
|
||||
},
|
||||
|
||||
handlePreferredApiBackend: function (backend) {
|
||||
this.updateBackendPreference(backend)
|
||||
if (backend === 'piped') {
|
||||
if (!this.backendFallback) {
|
||||
this.updateBackendFallback(true)
|
||||
}
|
||||
}
|
||||
|
||||
if (backend === 'local') {
|
||||
if (this.fallbackPreference === backend) {
|
||||
if (backend === 'invidious') {
|
||||
this.updateFallbackPreference('local')
|
||||
} else {
|
||||
this.updateFallbackPreference('invidious')
|
||||
}
|
||||
}
|
||||
|
||||
if (backend === 'local' || backend === 'piped') {
|
||||
this.updateForceLocalBackendForLegacy(false)
|
||||
}
|
||||
},
|
||||
|
||||
handleFallbackApiBackend: function (backend) {
|
||||
this.updateFallbackPreference(backend)
|
||||
},
|
||||
|
||||
...mapMutations([
|
||||
'setCurrentInvidiousInstance'
|
||||
'setCurrentInvidiousInstance',
|
||||
'setCurrentPipedInstance'
|
||||
]),
|
||||
|
||||
...mapActions([
|
||||
|
@ -207,7 +265,9 @@ export default defineComponent({
|
|||
'updateCheckForBlogPosts',
|
||||
'updateBarColor',
|
||||
'updateBackendPreference',
|
||||
'updateFallbackPreference',
|
||||
'updateDefaultInvidiousInstance',
|
||||
'updateDefaultPipedInstance',
|
||||
'updateLandingPage',
|
||||
'updateRegion',
|
||||
'updateListType',
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
<ft-toggle-switch
|
||||
:label="$t('Settings.General Settings.Fallback to Non-Preferred Backend on Failure')"
|
||||
:default-value="backendFallback"
|
||||
:disabled="backendPreference === 'piped'"
|
||||
:compact="true"
|
||||
:tooltip="$t('Tooltips.General Settings.Fallback to Non-Preferred Backend on Failure')"
|
||||
@change="updateBackendFallback"
|
||||
|
@ -42,6 +43,15 @@
|
|||
:tooltip="$t('Tooltips.General Settings.Preferred API Backend')"
|
||||
@change="handlePreferredApiBackend"
|
||||
/>
|
||||
<ft-select
|
||||
v-if="backendFallback"
|
||||
:placeholder="$t('Settings.General Settings.Preferred API Backend.Fallback API Backend')"
|
||||
:value="fallbackPreference"
|
||||
:select-names="backendNames"
|
||||
:select-values="backendValues"
|
||||
:tooltip="$t('Tooltips.General Settings.Fallback API Backend')"
|
||||
@change="handleFallbackApiBackend"
|
||||
/>
|
||||
<ft-select
|
||||
v-if="false"
|
||||
:placeholder="$t('Settings.General Settings.Default Landing Page')"
|
||||
|
@ -90,7 +100,7 @@
|
|||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="backendPreference === 'invidious' || backendFallback"
|
||||
v-if="backendPreference === 'invidious' || fallbackPreference === 'invidious'"
|
||||
>
|
||||
<ft-flex-box class="settingsFlexStart460px">
|
||||
<ft-input
|
||||
|
@ -137,6 +147,54 @@
|
|||
/>
|
||||
</ft-flex-box>
|
||||
</div>
|
||||
<div
|
||||
v-if="backendPreference === 'piped' || fallbackPreference === 'piped'"
|
||||
>
|
||||
<ft-flex-box class="settingsFlexStart460px">
|
||||
<ft-input
|
||||
:placeholder="$t('Settings.General Settings.Current Piped Instance')"
|
||||
:show-action-button="false"
|
||||
:show-label="true"
|
||||
:value="currentPipedInstance"
|
||||
:data-list="pipedInstancesList"
|
||||
:tooltip="$t('Tooltips.General Settings.Piped Instance')"
|
||||
@input="handlePipedInstanceInput"
|
||||
/>
|
||||
</ft-flex-box>
|
||||
<ft-flex-box>
|
||||
<div>
|
||||
<a
|
||||
href="https://github.com/TeamPiped/Piped/wiki/Instances"
|
||||
>
|
||||
{{ $t('Settings.General Settings.View all Piped instance information') }}
|
||||
</a>
|
||||
</div>
|
||||
</ft-flex-box>
|
||||
<p
|
||||
v-if="defaultPipedInstance !== ''"
|
||||
class="center"
|
||||
>
|
||||
{{ $t('Settings.General Settings.The currently set default instance is {instance}', { instance: defaultPipedInstance }) }}
|
||||
</p>
|
||||
<template v-else>
|
||||
<p class="center">
|
||||
{{ $t('Settings.General Settings.No default instance has been set') }}
|
||||
</p>
|
||||
<p class="center">
|
||||
{{ $t('Settings.General Settings.Current instance will be randomized on startup') }}
|
||||
</p>
|
||||
</template>
|
||||
<ft-flex-box>
|
||||
<ft-button
|
||||
:label="$t('Settings.General Settings.Set Current Instance as Default')"
|
||||
@click="handleSetDefaultPipedInstanceClick"
|
||||
/>
|
||||
<ft-button
|
||||
:label="$t('Settings.General Settings.Clear Default Instance')"
|
||||
@click="handleClearDefaultPipedInstanceClick"
|
||||
/>
|
||||
</ft-flex-box>
|
||||
</div>
|
||||
</ft-settings-section>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -8,11 +8,18 @@ export default defineComponent({
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
backendFallback: function () {
|
||||
return this.$store.getters.getBackendFallback
|
||||
},
|
||||
backendPreference: function () {
|
||||
return this.$store.getters.getBackendPreference
|
||||
let preference = this.$store.getters.getBackendPreference
|
||||
if (preference === 'piped') {
|
||||
preference = this.$store.getters.getFallbackPreference
|
||||
}
|
||||
return preference
|
||||
},
|
||||
fallbackPreference: function() {
|
||||
return this.$store.getters.getFallbackPreference
|
||||
},
|
||||
backendFallback: function () {
|
||||
return this.$store.getters.getBackendFallback && this.$store.getters.getBackendPreference !== 'piped'
|
||||
},
|
||||
hidePopularVideos: function () {
|
||||
return this.$store.getters.getHidePopularVideos
|
||||
|
|
|
@ -71,7 +71,7 @@
|
|||
</p>
|
||||
</router-link>
|
||||
<router-link
|
||||
v-if="!hidePopularVideos && (backendFallback || backendPreference === 'invidious')"
|
||||
v-if="!hidePopularVideos && ((backendFallback && fallbackPreference === 'invidious') || backendPreference === 'invidious')"
|
||||
class="navOption"
|
||||
:title="$t('Most Popular')"
|
||||
:aria-label="hideLabelsSideBar ? $t('Most Popular') : null"
|
||||
|
|
|
@ -13,11 +13,15 @@ export default defineComponent({
|
|||
isOpen: function () {
|
||||
return this.$store.getters.getIsSideNavOpen
|
||||
},
|
||||
backendFallback: function () {
|
||||
return this.$store.getters.getBackendFallback
|
||||
},
|
||||
backendPreference: function () {
|
||||
return this.$store.getters.getBackendPreference
|
||||
let preference = this.$store.getters.getBackendPreference
|
||||
if (preference === 'piped') {
|
||||
preference = this.$store.getters.getFallbackPreference
|
||||
}
|
||||
return preference
|
||||
},
|
||||
backendFallback: function () {
|
||||
return this.$store.getters.getBackendFallback && this.$store.getters.getBackendPreference !== 'piped'
|
||||
},
|
||||
currentInvidiousInstance: function () {
|
||||
return this.$store.getters.getCurrentInvidiousInstance
|
||||
|
|
|
@ -58,12 +58,16 @@ export default defineComponent({
|
|||
return this.$store.getters.getCurrentInvidiousInstance
|
||||
},
|
||||
|
||||
backendFallback: function () {
|
||||
return this.$store.getters.getBackendFallback
|
||||
backendPreference: function () {
|
||||
let preference = this.$store.getters.getBackendPreference
|
||||
if (preference === 'piped') {
|
||||
preference = this.$store.getters.getFallbackPreference
|
||||
}
|
||||
return preference
|
||||
},
|
||||
|
||||
backendPreference: function () {
|
||||
return this.$store.getters.getBackendPreference
|
||||
backendFallback: function () {
|
||||
return this.$store.getters.getBackendFallback && this.$store.getters.getBackendPreference !== 'piped'
|
||||
},
|
||||
|
||||
expandSideBar: function () {
|
||||
|
|
|
@ -44,12 +44,15 @@ export default defineComponent({
|
|||
},
|
||||
computed: {
|
||||
backendPreference: function () {
|
||||
return 'piped'
|
||||
// return this.$store.getters.getBackendPreference
|
||||
let preference = this.$store.getters.getBackendPreference
|
||||
if (preference === 'piped') {
|
||||
preference = this.$store.getters.getFallbackPreference
|
||||
}
|
||||
return preference
|
||||
},
|
||||
|
||||
backendFallback: function () {
|
||||
return this.$store.getters.getBackendFallback
|
||||
return this.$store.getters.getBackendFallback && this.$store.getters.getBackendPreference !== 'piped'
|
||||
},
|
||||
|
||||
hideCommentLikes: function () {
|
||||
|
|
|
@ -57,11 +57,15 @@ export default defineComponent({
|
|||
},
|
||||
computed: {
|
||||
backendPreference: function () {
|
||||
return this.$store.getters.getBackendPreference
|
||||
let preference = this.$store.getters.getBackendPreference
|
||||
if (preference === 'piped') {
|
||||
preference = this.$store.getters.getFallbackPreference
|
||||
}
|
||||
return preference
|
||||
},
|
||||
|
||||
backendFallback: function () {
|
||||
return this.$store.getters.getBackendFallback
|
||||
return this.$store.getters.getBackendFallback && this.$store.getters.getBackendPreference !== 'piped'
|
||||
},
|
||||
|
||||
chatHeight: function () {
|
||||
|
|
|
@ -6,6 +6,7 @@ import FtListVideoLazy from '../ft-list-video-lazy/ft-list-video-lazy.vue'
|
|||
import { copyToClipboard, showToast } from '../../helpers/utils'
|
||||
import { getLocalPlaylist, parseLocalPlaylistVideo } from '../../helpers/api/local'
|
||||
import { invidiousGetPlaylistInfo } from '../../helpers/api/invidious'
|
||||
import { getPipedPlaylist, getPipedPlaylistMore } from '../../helpers/api/piped'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'WatchVideoPlaylist',
|
||||
|
@ -42,6 +43,10 @@ export default defineComponent({
|
|||
return this.$store.getters.getBackendPreference
|
||||
},
|
||||
|
||||
fallbackPreference: function () {
|
||||
return this.$store.getters.getFallbackPreference
|
||||
},
|
||||
|
||||
backendFallback: function () {
|
||||
return this.$store.getters.getBackendFallback
|
||||
},
|
||||
|
@ -282,7 +287,24 @@ export default defineComponent({
|
|||
this.channelName = cachedPlaylist.channelName
|
||||
this.channelId = cachedPlaylist.channelId
|
||||
|
||||
if (!process.env.IS_ELECTRON || this.backendPreference === 'invidious' || cachedPlaylist.continuationData === null) {
|
||||
if (this.backendPreference === 'piped') {
|
||||
const items = cachedPlaylist.items
|
||||
let nextpage = cachedPlaylist.continuationData
|
||||
do {
|
||||
const moreInfo = await getPipedPlaylistMore({
|
||||
playlistId: cachedPlaylist.playlistId,
|
||||
nextpage
|
||||
})
|
||||
|
||||
items.push(moreInfo.videos)
|
||||
|
||||
if (!moreInfo.nextpage) {
|
||||
nextpage = null
|
||||
}
|
||||
} while (nextpage != null)
|
||||
|
||||
this.playlistItems = items
|
||||
} else if (!process.env.IS_ELECTRON || this.backendPreference === 'invidious' || cachedPlaylist.continuationData === null) {
|
||||
this.playlistItems = cachedPlaylist.items
|
||||
} else {
|
||||
const items = cachedPlaylist.items
|
||||
|
@ -334,8 +356,52 @@ export default defineComponent({
|
|||
copyToClipboard(err)
|
||||
})
|
||||
if (this.backendPreference === 'local' && this.backendFallback) {
|
||||
showToast(this.$t('Falling back to Invidious API'))
|
||||
this.getPlaylistInformationInvidious()
|
||||
if (this.fallbackPreference === 'invidious') {
|
||||
showToast(this.$t('Falling back to Invidious API'))
|
||||
this.getPlaylistInformationInvidious()
|
||||
} else {
|
||||
showToast(this.$t('Falling back to Piped API'))
|
||||
this.getPlaylistInformationPiped()
|
||||
}
|
||||
} else {
|
||||
this.isLoading = false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getPlaylistInformationPiped: async function() {
|
||||
this.isLoading = true
|
||||
try {
|
||||
const playlistInfo = await getPipedPlaylist(this.playlistId)
|
||||
this.playlistTitle = playlistInfo.playlist.title
|
||||
this.channelName = playlistInfo.playlist.channelName
|
||||
this.channelId = playlistInfo.playlist.channelId
|
||||
let nextpage = playlistInfo.nextpage
|
||||
const videos = playlistInfo.videos
|
||||
while (nextpage != null) {
|
||||
const playlistContInfo = await getPipedPlaylistMore({
|
||||
playlistId: this.playlistId,
|
||||
continuation: nextpage
|
||||
})
|
||||
nextpage = playlistContInfo.nextpage
|
||||
videos.push(...playlistContInfo.videos)
|
||||
}
|
||||
this.playlistItems = videos
|
||||
this.isLoading = false
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
const errorMessage = this.$t('Piped API Error (Click to copy)')
|
||||
showToast(`${errorMessage}: ${err}`, 10000, () => {
|
||||
copyToClipboard(err)
|
||||
})
|
||||
if (this.backendPreference === 'piped' && this.backendFallback) {
|
||||
if (this.fallbackPreference === 'invidious') {
|
||||
showToast(this.$t('Falling back to Invidious API'))
|
||||
this.getPlaylistInformationInvidious()
|
||||
} else {
|
||||
showToast(this.$t('Falling back to Piped API'))
|
||||
this.getPlaylistInformationLocal()
|
||||
}
|
||||
} else {
|
||||
this.isLoading = false
|
||||
}
|
||||
|
@ -358,9 +424,16 @@ export default defineComponent({
|
|||
showToast(`${errorMessage}: ${err}`, 10000, () => {
|
||||
copyToClipboard(err)
|
||||
})
|
||||
if (process.env.IS_ELECTRON && this.backendPreference === 'invidious' && this.backendFallback) {
|
||||
showToast(this.$t('Falling back to Local API'))
|
||||
this.getPlaylistInformationLocal()
|
||||
if (this.backendPreference === 'invidious' && this.backendFallback) {
|
||||
if (process.env.IS_ELECTRON && this.fallbackPreference === 'local') {
|
||||
showToast(this.$t('Falling back to Local API'))
|
||||
this.getPlaylistInformationLocal()
|
||||
} else if (this.fallbackPreference === 'piped') {
|
||||
showToast(this.$t('Falling back to Piped API'))
|
||||
this.getPlaylistInformationPiped()
|
||||
} else {
|
||||
this.isLoading = false
|
||||
}
|
||||
} else {
|
||||
this.isLoading = false
|
||||
// TODO: Show toast with error message
|
||||
|
|
|
@ -73,15 +73,30 @@ function parsePipedComments(comments) {
|
|||
}
|
||||
|
||||
export async function getPipedPlaylist(playlistId) {
|
||||
const pList = await pipedRequest({ resource: 'playlists', id: playlistId })
|
||||
console.error(pList)
|
||||
const parsedVideos = parsePipedVideos(pList.relatedStreams)
|
||||
const playlistInfo = await pipedRequest({ resource: 'playlists', id: playlistId })
|
||||
const parsedVideos = parsePipedVideos(playlistInfo.relatedStreams)
|
||||
return {
|
||||
playlist: parsePipedPlaylist(playlistId, pList, parsedVideos),
|
||||
nextpage: playlistInfo.nextpage,
|
||||
playlist: parsePipedPlaylist(playlistId, playlistInfo, parsedVideos),
|
||||
videos: parsedVideos
|
||||
}
|
||||
}
|
||||
|
||||
export async function getPipedPlaylistMore({ playlistId, continuation }) {
|
||||
const playlistInfo = await pipedRequest({
|
||||
resource: 'nextpage/playlists',
|
||||
id: playlistId,
|
||||
params: {
|
||||
nextpage: continuation
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
nextpage: playlistInfo.nextpage,
|
||||
videos: parsePipedVideos(playlistInfo.relatedStreams)
|
||||
}
|
||||
}
|
||||
|
||||
export function getPipedUrlInfo(url) {
|
||||
const regex = /^(?<baseUrl>.*)\/(?<imageProtocol>vi|ytc)\/(?<resource>[^?]*).*host=(?<host>[^&]*)/
|
||||
return url.match(regex).groups
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
import history from './history'
|
||||
import invidious from './invidious'
|
||||
import piped from './piped'
|
||||
import playlists from './playlists'
|
||||
import profiles from './profiles'
|
||||
import settings from './settings'
|
||||
|
@ -14,6 +15,7 @@ import utils from './utils'
|
|||
export default {
|
||||
history,
|
||||
invidious,
|
||||
piped,
|
||||
playlists,
|
||||
profiles,
|
||||
settings,
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
import fs from 'fs/promises'
|
||||
import { pathExists } from '../../helpers/filesystem'
|
||||
|
||||
const state = {
|
||||
currentPipedInstance: '',
|
||||
pipedInstancesList: null
|
||||
}
|
||||
|
||||
const getters = {
|
||||
getCurrentPipedInstance(state) {
|
||||
return state.currentPipedInstance
|
||||
},
|
||||
|
||||
getPipedInstancesList(state) {
|
||||
return state.pipedInstancesList
|
||||
}
|
||||
}
|
||||
|
||||
const actions = {
|
||||
async fetchPipedInstances({ commit }) {
|
||||
const requestUrl = 'https://piped-instances.kavin.rocks/'
|
||||
|
||||
let instances = []
|
||||
try {
|
||||
const response = await (await fetch(requestUrl)).json()
|
||||
instances = response.map(instance => instance.api_url)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
// If the piped instance fetch isn't returning anything interpretable
|
||||
if (instances.length === 0) {
|
||||
// Fallback: read from static file
|
||||
const fileName = 'piped-instances.json'
|
||||
/* eslint-disable-next-line n/no-path-concat */
|
||||
const fileLocation = process.env.NODE_ENV === 'development' ? './static/' : `${__dirname}/static/`
|
||||
if (await pathExists(`${fileLocation}${fileName}`)) {
|
||||
console.warn('reading static file for piped instances')
|
||||
const fileData = await fs.readFile(`${fileLocation}${fileName}`)
|
||||
instances = JSON.parse(fileData)
|
||||
}
|
||||
}
|
||||
commit('setPipedInstancesList', instances)
|
||||
},
|
||||
|
||||
setRandomCurrentPipedInstance({ commit, state }) {
|
||||
const instanceList = state.pipedInstancesList
|
||||
const randomIndex = Math.floor(Math.random() * instanceList.length)
|
||||
commit('setCurrentPipedInstance', instanceList[randomIndex])
|
||||
}
|
||||
}
|
||||
|
||||
const mutations = {
|
||||
setCurrentPipedInstance(state, value) {
|
||||
state.currentPipedInstance = value
|
||||
},
|
||||
|
||||
setPipedInstancesList(state, value) {
|
||||
state.pipedInstancesList = value
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
state,
|
||||
getters,
|
||||
actions,
|
||||
mutations
|
||||
}
|
|
@ -165,6 +165,7 @@ const state = {
|
|||
autoplayVideos: true,
|
||||
backendFallback: true,
|
||||
backendPreference: 'local',
|
||||
fallbackPreference: 'invidious',
|
||||
barColor: false,
|
||||
checkForBlogPosts: true,
|
||||
checkForUpdates: true,
|
||||
|
@ -349,6 +350,15 @@ const stateWithSideEffects = {
|
|||
}
|
||||
},
|
||||
|
||||
defaultPipedInstance: {
|
||||
defaultValue: '', // s
|
||||
sideEffectsHandler: ({ commit, getters }, value) => {
|
||||
if (value !== '' && getters.getCurrentInvidiousInstance !== value) {
|
||||
commit('setCurrentPipedInstance', value)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
defaultVolume: {
|
||||
defaultValue: 1,
|
||||
sideEffectsHandler: (_, value) => {
|
||||
|
|
|
@ -104,11 +104,15 @@ export default defineComponent({
|
|||
},
|
||||
computed: {
|
||||
backendPreference: function () {
|
||||
return this.$store.getters.getBackendPreference
|
||||
let preference = this.$store.getters.getBackendPreference
|
||||
if (preference === 'piped') {
|
||||
preference = this.$store.getters.getFallbackPreference
|
||||
}
|
||||
return preference
|
||||
},
|
||||
|
||||
backendFallback: function () {
|
||||
return this.$store.getters.getBackendFallback
|
||||
return this.$store.getters.getBackendFallback && this.$store.getters.getBackendPreference !== 'piped'
|
||||
},
|
||||
|
||||
hideUnsubscribeButton: function() {
|
||||
|
|
|
@ -9,7 +9,7 @@ import FtButton from '../../components/ft-button/ft-button.vue'
|
|||
import { getLocalPlaylist, parseLocalPlaylistVideo } from '../../helpers/api/local'
|
||||
import { extractNumberFromString } from '../../helpers/utils'
|
||||
import { invidiousGetPlaylistInfo, youtubeImageUrlToInvidious } from '../../helpers/api/invidious'
|
||||
import { getPipedPlaylist, pipedImageToYouTube } from '../../helpers/api/piped'
|
||||
import { getPipedPlaylist, getPipedPlaylistMore, pipedImageToYouTube } from '../../helpers/api/piped'
|
||||
export default defineComponent({
|
||||
name: 'Playlist',
|
||||
components: {
|
||||
|
@ -168,9 +168,9 @@ export default defineComponent({
|
|||
|
||||
getPlaylistPiped: async function () {
|
||||
this.isLoading = true
|
||||
const { playlist, videos } = await getPipedPlaylist(this.playlistId)
|
||||
const { playlist, videos, nextpage } = await getPipedPlaylist(this.playlistId)
|
||||
this.infoData = playlist
|
||||
|
||||
this.continuationData = nextpage
|
||||
this.playlistItems = this.playlistItems.concat(videos)
|
||||
|
||||
this.updateSubscriptionDetails({
|
||||
|
@ -189,6 +189,9 @@ export default defineComponent({
|
|||
case 'invidious':
|
||||
console.error('Playlist pagination is not currently supported when the Invidious backend is selected.')
|
||||
break
|
||||
case 'piped':
|
||||
this.getNextPagePiped()
|
||||
break
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -209,6 +212,22 @@ export default defineComponent({
|
|||
})
|
||||
},
|
||||
|
||||
getNextPagePiped: async function() {
|
||||
this.isLoadingMore = true
|
||||
const { videos, nextpage } = await getPipedPlaylistMore({
|
||||
playlistId: this.playlistId,
|
||||
continuation: this.continuationData
|
||||
})
|
||||
|
||||
this.playlistItems = this.playlistItems.concat(videos)
|
||||
if (nextpage) {
|
||||
this.continuationData = nextpage
|
||||
} else {
|
||||
this.continuationData = null
|
||||
}
|
||||
this.isLoadingMore = false
|
||||
},
|
||||
|
||||
...mapActions([
|
||||
'updateSubscriptionDetails'
|
||||
]),
|
||||
|
|
|
@ -31,11 +31,15 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
backendPreference: function () {
|
||||
return this.$store.getters.getBackendPreference
|
||||
let preference = this.$store.getters.getBackendPreference
|
||||
if (preference === 'piped') {
|
||||
preference = this.$store.getters.getFallbackPreference
|
||||
}
|
||||
return preference
|
||||
},
|
||||
|
||||
backendFallback: function () {
|
||||
return this.$store.getters.getBackendFallback
|
||||
return this.$store.getters.getBackendFallback && this.$store.getters.getBackendPreference !== 'piped'
|
||||
},
|
||||
|
||||
showFamilyFriendlyOnly: function() {
|
||||
|
|
|
@ -35,11 +35,15 @@ export default defineComponent({
|
|||
},
|
||||
computed: {
|
||||
backendPreference: function () {
|
||||
return this.$store.getters.getBackendPreference
|
||||
let preference = this.$store.getters.getBackendPreference
|
||||
if (preference === 'piped') {
|
||||
preference = this.$store.getters.getFallbackPreference
|
||||
}
|
||||
return preference
|
||||
},
|
||||
|
||||
backendFallback: function () {
|
||||
return this.$store.getters.getBackendFallback
|
||||
return this.$store.getters.getBackendFallback && this.$store.getters.getBackendPreference !== 'piped'
|
||||
},
|
||||
|
||||
currentInvidiousInstance: function () {
|
||||
|
|
|
@ -28,10 +28,14 @@ export default defineComponent({
|
|||
},
|
||||
computed: {
|
||||
backendPreference: function () {
|
||||
return this.$store.getters.getBackendPreference
|
||||
let preference = this.$store.getters.getBackendPreference
|
||||
if (preference === 'piped') {
|
||||
preference = this.$store.getters.getFallbackPreference
|
||||
}
|
||||
return preference
|
||||
},
|
||||
backendFallback: function () {
|
||||
return this.$store.getters.getBackendFallback
|
||||
return this.$store.getters.getBackendFallback && this.$store.getters.getBackendPreference !== 'piped'
|
||||
},
|
||||
region: function () {
|
||||
return this.$store.getters.getRegion.toUpperCase()
|
||||
|
|
|
@ -117,10 +117,14 @@ export default defineComponent({
|
|||
return this.$store.getters.getSaveVideoHistoryWithLastViewedPlaylist
|
||||
},
|
||||
backendPreference: function () {
|
||||
return this.$store.getters.getBackendPreference
|
||||
let preference = this.$store.getters.getBackendPreference
|
||||
if (preference === 'piped') {
|
||||
preference = this.$store.getters.getFallbackPreference
|
||||
}
|
||||
return preference
|
||||
},
|
||||
backendFallback: function () {
|
||||
return this.$store.getters.getBackendFallback
|
||||
return this.$store.getters.getBackendFallback && this.$store.getters.getBackendPreference !== 'piped'
|
||||
},
|
||||
currentInvidiousInstance: function () {
|
||||
return this.$store.getters.getCurrentInvidiousInstance
|
||||
|
|
|
@ -145,8 +145,10 @@ Settings:
|
|||
System Default: System Default
|
||||
Preferred API Backend:
|
||||
Preferred API Backend: Preferred API Backend
|
||||
Fallback API Backend: Fallback API Backend
|
||||
Local API: Local API
|
||||
Invidious API: Invidious API
|
||||
Piped API: Piped API
|
||||
Video View Type:
|
||||
Video View Type: Video View Type
|
||||
Grid: Grid
|
||||
|
@ -158,12 +160,14 @@ Settings:
|
|||
Middle: Middle
|
||||
End: End
|
||||
Current Invidious Instance: Current Invidious Instance
|
||||
Current Piped Instance: Current Piped Instance
|
||||
The currently set default instance is {instance}: The currently set default instance is {instance}
|
||||
No default instance has been set: No default instance has been set
|
||||
Current instance will be randomized on startup: Current instance will be randomized on startup
|
||||
Set Current Instance as Default: Set Current Instance as Default
|
||||
Clear Default Instance: Clear Default Instance
|
||||
View all Invidious instance information: View all Invidious instance information
|
||||
View all Piped instance information: View all Piped instance information
|
||||
Region for Trending: Region for Trending
|
||||
#! List countries
|
||||
External Link Handling:
|
||||
|
@ -780,14 +784,17 @@ Tooltips:
|
|||
General Settings:
|
||||
Preferred API Backend: Choose the backend that FreeTube uses to obtain data. The
|
||||
local API is a built-in extractor. The Invidious API requires an Invidious server
|
||||
to connect to.
|
||||
to connect to. The Piped API requires a Piped server to connect to.
|
||||
Fallback to Non-Preferred Backend on Failure: When your preferred API has a problem,
|
||||
FreeTube will automatically attempt to use your non-preferred API as a fallback
|
||||
method when enabled.
|
||||
Fallback API Backend: Choose the fallback backend that FreeTube uses when there is a problem with the preferred API.
|
||||
Thumbnail Preference: All thumbnails throughout FreeTube will be replaced with
|
||||
a frame of the video instead of the default thumbnail.
|
||||
Invidious Instance: The Invidious instance that FreeTube will connect to for API
|
||||
calls.
|
||||
Piped Instance: The Piped instance that FreeTube will connect to for API
|
||||
calls.
|
||||
Region for Trending: The region of trends allows you to pick which country's trending
|
||||
videos you want to have displayed. Not all countries displayed are actually
|
||||
supported by YouTube.
|
||||
|
@ -842,6 +849,7 @@ Tooltips:
|
|||
Local API Error (Click to copy): Local API Error (Click to copy)
|
||||
Invidious API Error (Click to copy): Invidious API Error (Click to copy)
|
||||
Falling back to Invidious API: Falling back to Invidious API
|
||||
Falling back to Piped API: Falling back to Piped API
|
||||
Falling back to the local API: Falling back to the local API
|
||||
This video is unavailable because of missing formats. This can happen due to country unavailability.: This
|
||||
video is unavailable because of missing formats. This can happen due to country
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
["https://pipedapi.kavin.rocks","https://pipedapi.tokhmi.xyz","https://api-piped.mha.fi","https://watchapi.whatever.social","https://piped-api.garudalinux.org","https://pipedapi.leptons.xyz","https://piped-api.lunar.icu","https://ytapi.dc09.ru","https://pipedapi-libre.kavin.rocks","https://pa.mint.lgbt","https://pa.il.ax","https://piped-api.privacy.com.de","https://pipedapi.esmailelbob.xyz","https://api.piped.projectsegfau.lt","https://pipedapi.in.projectsegfau.lt","https://api.piped.privacydev.net","https://pipedapi.palveluntarjoaja.eu","https://pipedapi.smnz.de","https://pipedapi.adminforge.de","https://pipedapi.qdi.fi","https://piped-api.hostux.net","https://pipedapi.chauvet.pro","https://pipedapi.jotoma.de","https://pipedapi.pfcd.me","https://pipedapi.frontendfriendly.xyz"]
|
Loading…
Reference in New Issue