add piped settings + fallback when not supported

This commit is contained in:
chunky programmer 2023-05-08 20:42:29 -04:00 committed by Chunky programmer
parent 17c7510f90
commit 3633a86ac3
26 changed files with 468 additions and 64 deletions

View File

@ -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))
})

View File

@ -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 ./",

View File

@ -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',

View File

@ -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

View File

@ -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 () {

View File

@ -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',

View File

@ -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>

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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 () {

View File

@ -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 () {

View File

@ -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 () {

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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
}

View File

@ -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) => {

View File

@ -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() {

View File

@ -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'
]),

View File

@ -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() {

View File

@ -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 () {

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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"]