Compare commits

...

102 Commits

Author SHA1 Message Date
ChunkyProgrammer dff0e9ff75
Merge aaefcd17db into ec5ca2bb66 2024-04-26 12:07:38 +00:00
summoner001 ec5ca2bb66
Translated using Weblate (Hungarian)
Currently translated at 100.0% (830 of 830 strings)

Translation: FreeTube/Translations
Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/hu/
2024-04-26 14:07:18 +02:00
gallegonovato b7e94669cb
Translated using Weblate (Spanish)
Currently translated at 100.0% (830 of 830 strings)

Translation: FreeTube/Translations
Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/es/
2024-04-26 14:07:17 +02:00
Fjuro ce57393549
Translated using Weblate (Czech)
Currently translated at 100.0% (830 of 830 strings)

Translation: FreeTube/Translations
Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/cs/
2024-04-26 12:07:09 +02:00
Oğuz Ersen f5c89f3f9f
Translated using Weblate (Turkish)
Currently translated at 100.0% (830 of 830 strings)

Translation: FreeTube/Translations
Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/tr/
2024-04-26 09:07:16 +02:00
Massimo Pissarello a2d67523ab
Translated using Weblate (Italian)
Currently translated at 100.0% (830 of 830 strings)

Translation: FreeTube/Translations
Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/it/
2024-04-26 09:07:14 +02:00
Jose Delvani fc123f3792
Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (830 of 830 strings)

Translation: FreeTube/Translations
Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/pt_BR/
2024-04-26 09:07:13 +02:00
大王叫我来巡山 1bd3ee936a
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (830 of 830 strings)

Translation: FreeTube/Translations
Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/zh_Hans/
2024-04-26 09:07:11 +02:00
ChunkyProgrammer aaefcd17db Merge branch 'development' into piped-support 2024-04-18 21:23:11 -04:00
ChunkyProgrammer 4938335635 update warn message 2024-04-18 21:14:25 -04:00
ChunkyProgrammer e427b81c49 fix lint error 2024-04-17 21:42:44 -04:00
ChunkyProgrammer 147600b698 update piped instance list 2024-04-17 21:41:06 -04:00
ChunkyProgrammer ff715e9c88 Merge branch 'development' into piped-support 2024-04-17 21:39:45 -04:00
ChunkyProgrammer 742a390fd8 Make suggested changes
Co-Authored-By: Emma <MarmadileManteater@proton.me>
Co-Authored-By: Jason <84899178+jasonhenriquez@users.noreply.github.com>
2024-04-17 21:39:37 -04:00
ChunkyProgrammer f0733a7662 Merge branch 'development' into piped-support 2024-04-17 17:10:51 -04:00
ChunkyProgrammer 0db77be01d Hide playlist view count for piped 2024-04-16 17:59:53 -04:00
ChunkyProgrammer e1f040a5f2 add index to videoId for playlist video list 2024-04-16 17:16:17 -04:00
ChunkyProgrammer 8883f8a2a8 Merge branch 'development' into piped-support 2024-04-16 16:56:03 -04:00
ChunkyProgrammer 58d3d7e48f Merge branch 'development' into piped-support 2024-04-12 08:52:45 -04:00
ChunkyProgrammer ca588c77e2 Merge branch 'development' into piped-support 2024-04-12 08:43:55 -04:00
ChunkyProgrammer 53ceba0d13 Merge branch 'development' into piped-support 2024-04-04 18:47:44 -04:00
ChunkyProgrammer 469f9282ee update piped instances 2024-04-02 17:15:29 -04:00
ChunkyProgrammer f6ba3c9fc9 update components that are missing updates 2024-04-02 17:14:19 -04:00
ChunkyProgrammer 8c75d8fbfb Merge branch 'development' into piped-support 2024-04-02 17:09:39 -04:00
ChunkyProgrammer 9c8df73892 Merge branch 'development' into piped-support 2024-03-25 10:31:29 -04:00
ChunkyProgrammer 40cc1d404b Merge branch 'development' into piped-support 2024-03-13 16:39:00 -04:00
ChunkyProgrammer 6fc7cb0b44 Merge branch 'development' into piped-support 2024-03-07 20:17:43 -05:00
ChunkyProgrammer 461b0f6632 Merge branch 'development' into piped-support 2024-02-18 19:34:49 -05:00
ChunkyProgrammer a749d189b7 update static piped instances 2024-02-13 12:25:28 -05:00
ChunkyProgrammer 86ef3b1c6a Merge branch 'development' into piped-support 2024-02-13 12:24:27 -05:00
ChunkyProgrammer 2b2d677549 Merge branch 'development' into piped-support 2024-02-07 14:11:30 -05:00
ChunkyProgrammer 1b17f6bc34 Merge branch 'development' into piped-support 2024-01-18 01:11:09 -05:00
ChunkyProgrammer 4fa1d23e7c Merge branch 'development' into piped-support 2024-01-10 22:23:10 -05:00
ChunkyProgrammer 44c5d2aa95 fix error where videos in previous playlist appear 2024-01-10 22:22:55 -05:00
ChunkyProgrammer e445500cb5 Merge branch 'development' into piped-support and fix errors 2024-01-03 20:19:12 -05:00
ChunkyProgrammer 915a30d950 Merge branch 'development' into piped-support 2024-01-01 20:40:47 -05:00
ChunkyProgrammer 99dac6dea6 Merge branch 'development' into piped-support 2023-12-26 21:51:45 -05:00
ChunkyProgrammer dd18e516e1 updated pipe instances 2023-12-18 17:16:29 -05:00
ChunkyProgrammer 25846f8c02 Merge branch 'development' into piped-support 2023-12-18 13:57:23 -05:00
ChunkyProgrammer 784f495a80 Merge branch 'development' into piped-support 2023-12-04 17:11:46 -05:00
ChunkyProgrammer d4b2ac9329 Merge branch 'development' into piped-support 2023-11-26 13:32:09 -05:00
ChunkyProgrammer b509adaac0 Merge branch 'development' into piped-support 2023-11-06 15:57:52 -05:00
ChunkyProgrammer 2ae441dc28 Merge branch 'development' into piped-support 2023-11-01 07:58:56 -04:00
ChunkyProgrammer 2151c1cd59 Comments: populate isOwner field, add authorId 2023-10-31 18:23:12 -04:00
ChunkyProgrammer f9b791e8f6 use fetchWithTimeout for piped instances 2023-10-30 22:41:44 -04:00
ChunkyProgrammer ab6bcc023b Merge branch 'development' into piped-support 2023-10-30 22:20:35 -04:00
ChunkyProgrammer fe4032bbf2 Merge branch 'development' into piped-support 2023-10-30 22:19:14 -04:00
ChunkyProgrammer bb6f32127f fix small parsing issues, updated static instance list 2023-10-25 00:31:07 -04:00
ChunkyProgrammer a8998b9269 Add Piped support for trending page 2023-10-24 18:24:48 -04:00
ChunkyProgrammer 2c50ca4b7a Merge branch 'development' into piped-support 2023-10-24 17:31:14 -04:00
ChunkyProgrammer 533c9da3b7 Merge branch 'development' into piped-support 2023-08-29 17:27:55 -07:00
ChunkyProgrammer 04eb9c6a1d Merge branch 'development' into piped-support 2023-08-26 08:43:08 -07:00
ChunkyProgrammer a757a44773 fix loading cached playlist 2023-08-21 14:39:53 -07:00
ChunkyProgrammer 550a9abe5c fix playlist fallback for piped 2023-08-21 09:46:01 -07:00
ChunkyProgrammer 22be652c54 check apiUsed when fetching more comments 2023-08-21 09:42:42 -07:00
ChunkyProgrammer 6647e83d6b fix getInstances.js 2023-08-21 06:23:58 -07:00
ChunkyProgrammer aa7f21c97f update isNullOrEmpty imports 2023-08-21 06:21:07 -07:00
ChunkyProgrammer 863c1d2572 Merge branch 'development' into piped-support 2023-08-21 06:06:17 -07:00
ChunkyProgrammer d530d71eab update subscription tabs to work when piped is primary api 2023-07-26 12:05:50 -07:00
ChunkyProgrammer d9e208c555 Merge branch 'development' into piped-support 2023-07-26 11:58:46 -07:00
Chunky programmer 36105af0f9 Merge branch 'development' into piped-support 2023-07-04 14:19:42 -04:00
Chunky programmer 7370f689f9 Merge branch 'development' into piped-support 2023-07-02 10:32:29 -04:00
ChunkyProgrammer 0e88d70987 Merge branch 'development' into piped-support 2023-06-26 11:47:58 -07:00
ChunkyProgrammer e759679c81 Merge branch 'development' into piped-support 2023-06-19 17:00:05 -07:00
Chunky programmer 59dfdb2857 fix issue when nextpage is null/undefined 2023-05-30 20:46:49 -04:00
Chunky programmer 0614bfad39 add piped description, use piped for playlist thumbnail 2023-05-30 20:43:46 -04:00
Chunky programmer 66578d25af Merge branch 'development' into piped-support 2023-05-30 20:02:17 -04:00
Chunky programmer f58490aca5 add error checking before parsing 2023-05-22 18:24:18 -04:00
Chunky programmer eb1bf9c75c fix tooltip 2023-05-22 18:17:19 -04:00
Chunky programmer 3cafb80798 Merge branch 'development' into piped-support 2023-05-22 17:55:09 -04:00
Chunky programmer 3300118ff8 add piped to get instances script 2023-05-20 10:55:58 -04:00
Chunky programmer f69416c527 Merge branch 'development' into piped-support 2023-05-20 10:55:37 -04:00
Chunky programmer 5f92e0732c Merge branch 'piped-support' of https://github.com/ChunkyProgrammer/FreeTube into piped-support 2023-05-16 00:16:06 -04:00
Chunky programmer add031b79a fix clear default instance, fix toast message 2023-05-16 00:15:18 -04:00
chunky programmer 48601a64f1 simplify v-if
Co-Authored-By: PikachuEXE <pikachuexe@gmail.com>
2023-05-16 00:08:37 -04:00
chunky programmer 13d3acca1b Fix incorrect toast message
Co-Authored-By: PikachuEXE <pikachuexe@gmail.com>
2023-05-16 00:08:36 -04:00
ChunkyProgrammer 0801c07d0b Fix Invidious fallback for piped playlists
Co-authored-by: PikachuEXE <pikachuexe@gmail.com>
2023-05-16 00:08:35 -04:00
chunky programmer 31704e0fba hide dash on playlist info for piped 2023-05-16 00:08:34 -04:00
chunky programmer f109da2784 create ft-instance-selector component 2023-05-16 00:08:34 -04:00
chunky programmer 632a7c8c24 fix piped to yt image conversion, hide missing playlist data 2023-05-16 00:08:33 -04:00
chunky programmer a581bd04f3 format piped instances json
Co-Authored-By: absidue <48293849+absidue@users.noreply.github.com>
2023-05-16 00:08:32 -04:00
chunky programmer 315e96bc4f Revert "fix displaying playlist thumbnails"
This reverts commit 379b593283.
2023-05-16 00:08:31 -04:00
chunky programmer 92a3bbdaa0 fix displaying playlist thumbnails 2023-05-16 00:08:31 -04:00
chunky programmer d5f9517073 prevent same api from being used for backend + fallback 2023-05-16 00:08:30 -04:00
chunky programmer 3b4ee5990f fix comment and playlist fallbacks 2023-05-16 00:08:30 -04:00
chunky programmer 3633a86ac3 add piped settings + fallback when not supported 2023-05-16 00:08:29 -04:00
chunky programmer 17c7510f90 add piped playlist support 2023-05-16 00:08:27 -04:00
chunky programmer 5ef7627195 Allow using piped for comments 2023-05-16 00:08:26 -04:00
chunky programmer b883d8328d simplify v-if
Co-Authored-By: PikachuEXE <pikachuexe@gmail.com>
2023-05-10 22:32:45 -04:00
chunky programmer c5244b0bd4 Fix incorrect toast message
Co-Authored-By: PikachuEXE <pikachuexe@gmail.com>
2023-05-10 21:46:54 -04:00
ChunkyProgrammer 44e82ac65f
Fix Invidious fallback for piped playlists
Co-authored-by: PikachuEXE <pikachuexe@gmail.com>
2023-05-10 21:44:02 -04:00
chunky programmer 34a1706aa5 hide dash on playlist info for piped 2023-05-10 16:50:43 -04:00
chunky programmer 5491ef621d create ft-instance-selector component 2023-05-10 16:50:14 -04:00
chunky programmer 7e5bb17973 fix piped to yt image conversion, hide missing playlist data 2023-05-09 16:41:32 -04:00
chunky programmer 34f483cce5 format piped instances json
Co-Authored-By: absidue <48293849+absidue@users.noreply.github.com>
2023-05-09 12:42:47 -04:00
chunky programmer b23256734d Revert "fix displaying playlist thumbnails"
This reverts commit 379b593283.
2023-05-08 21:50:45 -04:00
chunky programmer 379b593283 fix displaying playlist thumbnails 2023-05-08 21:37:08 -04:00
chunky programmer 7550f5983b prevent same api from being used for backend + fallback 2023-05-08 21:37:07 -04:00
chunky programmer 98fdaa3605 fix comment and playlist fallbacks 2023-05-08 21:37:06 -04:00
chunky programmer 1ffcc5584e add piped settings + fallback when not supported 2023-05-08 21:23:57 -04:00
chunky programmer beb3e5538f add piped playlist support 2023-05-08 21:23:56 -04:00
chunky programmer 5d903ed4c2 Allow using piped for comments 2023-05-08 21:23:55 -04:00
46 changed files with 1108 additions and 160 deletions

View File

@ -14,3 +14,10 @@ fetch(invidiousApiUrl).then(e => e.json()).then(res => {
})
fs.writeFile('././static/invidious-instances.json', JSON.stringify(data, null, 2))
})
const pipedApiUrl = 'https://piped-instances.kavin.rocks/'
fetch(pipedApiUrl).then(e => e.json()).then(res => {
const data = res.map(e => e.api_url)
fs.writeFile('././static/piped-instances.json', JSON.stringify(data, null, 2))
})

View File

@ -97,6 +97,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
@ -159,9 +162,17 @@ export default defineComponent({
this.grabUserSettings().then(async () => {
this.checkThemeSettings()
await this.fetchInvidiousInstances()
await Promise.allSettled([
this.fetchInvidiousInstances(),
this.fetchPipedInstances()
])
if (this.defaultInvidiousInstance === '') {
await this.setRandomCurrentInvidiousInstance()
this.setRandomCurrentInvidiousInstance()
}
if (this.defaultPipedInstance === '') {
this.setRandomCurrentPipedInstance()
}
this.grabAllProfiles(this.$t('Profile.All Channels')).then(async () => {
@ -539,6 +550,8 @@ export default defineComponent({
'getExternalPlayerCmdArgumentsData',
'fetchInvidiousInstances',
'setRandomCurrentInvidiousInstance',
'fetchPipedInstances',
'setRandomCurrentPipedInstance',
'setupListenersToSyncWindows',
'updateBaseTheme',
'updateMainColor',

View File

@ -47,10 +47,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

@ -0,0 +1,51 @@
import { defineComponent } from 'vue'
import FtFlexBox from '../ft-flex-box/ft-flex-box.vue'
import FtInput from '../ft-input/ft-input.vue'
import FtButton from '../ft-button/ft-button.vue'
export default defineComponent({
name: 'FtInstanceSelector',
components: {
'ft-button': FtButton,
'ft-flex-box': FtFlexBox,
'ft-input': FtInput
},
props: {
placeholder: {
type: String,
required: true
},
tooltip: {
type: String,
required: true
},
backendType: {
type: String,
required: true
},
currentInstance: {
type: String,
required: true
},
instanceList: {
type: Array,
required: true
},
defaultInstance: {
type: String,
required: true
}
},
emits: ['clearDefaultInstance', 'input', 'setDefaultInstance'],
methods: {
handleInstanceInput: function (inputData) {
this.$emit('input', inputData)
},
setDefaultInstance: function () {
this.$emit('setDefaultInstance')
},
clearDefaultInstance: function () {
this.$emit('clearDefaultInstance')
}
}
})

View File

@ -0,0 +1,65 @@
<template>
<div>
<ft-flex-box
class="settingsFlexStart460px"
>
<ft-input
:placeholder="placeholder"
:show-action-button="false"
:show-label="true"
:value="currentInstance"
:data-list="instanceList"
:tooltip="tooltip"
@input="handleInstanceInput"
/>
</ft-flex-box>
<ft-flex-box>
<div v-if="backendType === 'piped'">
<a href="https://github.com/TeamPiped/Piped/wiki/Instances">
{{ $t('Settings.General Settings.View all Piped instance information') }}
</a>
</div>
<div
v-else-if="backendType === 'invidious'"
>
<a
href="https://api.invidious.io"
>
{{ $t('Settings.General Settings.View all Invidious instance information') }}
</a>
</div>
</ft-flex-box>
<p
v-if="defaultInstance !== ''"
class="center"
>
{{ $t('Settings.General Settings.The currently set default instance is {instance}', {
instance: defaultInstance
}) }}
</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="setDefaultInstance"
/>
<ft-button
:label="$t('Settings.General Settings.Clear Default Instance')"
@click="clearDefaultInstance"
/>
</ft-flex-box>
</div>
</template>
<script src="./ft-instance-selector"></script>

View File

@ -137,6 +137,10 @@ export default defineComponent({
return this.$store.getters.getBackendPreference
},
fallbackPreference: function () {
return this.$store.getters.getFallbackPreference
},
currentInvidiousInstance: function () {
return this.$store.getters.getCurrentInvidiousInstance
},
@ -319,23 +323,40 @@ export default defineComponent({
return this.deArrowCache.thumbnail
}
let baseUrl
if (this.backendPreference === 'invidious') {
let baseUrl = ''
let backendPreference = this.backendPreference
if (backendPreference === 'piped') {
if (this.data.thumbnail) {
return this.data.thumbnail
} else {
// this should be removed once piped supports more endpoints
backendPreference = this.fallbackPreference
}
}
if (!process.env.IS_ELECTRON || 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`
}
return imageUrl
},
hideVideoViews: function () {

View File

@ -41,7 +41,11 @@ 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
},
currentInvidiousInstance: function () {
return this.$store.getters.getCurrentInvidiousInstance

View File

@ -34,7 +34,11 @@ 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
},
currentInvidiousInstance: function () {
return this.$store.getters.getCurrentInvidiousInstance

View File

@ -2,10 +2,8 @@ import { defineComponent } from 'vue'
import { mapActions, mapMutations } from 'vuex'
import FtSettingsSection from '../ft-settings-section/ft-settings-section.vue'
import FtSelect from '../ft-select/ft-select.vue'
import FtInput from '../ft-input/ft-input.vue'
import FtToggleSwitch from '../ft-toggle-switch/ft-toggle-switch.vue'
import FtFlexBox from '../ft-flex-box/ft-flex-box.vue'
import FtButton from '../ft-button/ft-button.vue'
import FtInstanceSelector from '../ft-instance-selector/ft-instance-selector.vue'
import debounce from 'lodash.debounce'
import allLocales from '../../../../static/locales/activeLocales.json'
@ -17,20 +15,20 @@ export default defineComponent({
components: {
'ft-settings-section': FtSettingsSection,
'ft-select': FtSelect,
'ft-input': FtInput,
'ft-toggle-switch': FtToggleSwitch,
'ft-flex-box': FtFlexBox,
'ft-button': FtButton
'ft-instance-selector': FtInstanceSelector
},
data: function () {
return {
backendValues: process.env.SUPPORTS_LOCAL_API
? [
'invidious',
'local'
'local',
'piped'
]
: [
'invidious'
'invidious',
'piped'
],
viewTypeValues: [
'grid',
@ -61,6 +59,9 @@ export default defineComponent({
}
},
computed: {
currentPipedInstance: function () {
return this.$store.getters.getCurrentPipedInstance
},
currentInvidiousInstance: function () {
return this.$store.getters.getCurrentInvidiousInstance
},
@ -70,6 +71,9 @@ export default defineComponent({
backendFallback: function () {
return this.$store.getters.getBackendFallback
},
fallbackPreference: function () {
return this.$store.getters.getFallbackPreference
},
blurThumbnails: function () {
return this.$store.getters.getBlurThumbnails
},
@ -141,6 +145,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
},
generalAutoLoadMorePaginatedItemsEnabled() {
return this.$store.getters.getGeneralAutoLoadMorePaginatedItemsEnabled
},
@ -163,11 +173,13 @@ export default defineComponent({
if (process.env.SUPPORTS_LOCAL_API) {
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')
]
} else {
return [
this.$t('Settings.General Settings.Preferred API Backend.Invidious API')
this.$t('Settings.General Settings.Preferred API Backend.Invidious API'),
this.$t('Settings.General Settings.Preferred API Backend.Piped API')
]
}
},
@ -205,18 +217,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) {
@ -228,7 +249,11 @@ export default defineComponent({
this.setCurrentInvidiousInstanceBounce(instance)
},
handleSetDefaultInstanceClick: function () {
handlePipedInstanceInput: function (input) {
this.setCurrentPipedInstanceBounce(input)
},
handleSetDefaultInvidiousInstanceClick: function () {
const instance = this.currentInvidiousInstance
this.updateDefaultInvidiousInstance(instance)
@ -236,26 +261,64 @@ export default defineComponent({
showToast(message)
},
handleClearDefaultInstanceClick: function () {
handleSetDefaultPipedInstanceClick: function () {
const instance = this.currentPipedInstance
this.updateDefaultPipedInstance(instance)
const message = this.$t('Default Piped instance has been set to {instance}', { instance })
showToast(message)
},
handleClearDefaultInvidiousInstanceClick: 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)
if (this.backendPreference === backend) {
if (backend === 'invidious') {
this.handlePreferredApiBackend('local')
} else {
this.handlePreferredApiBackend('invidious')
}
}
},
handleThumbnailPreferenceChange: function (value) {
this.updateBlurThumbnails(value === 'blur')
this.updateThumbnailPreference(value)
},
...mapMutations([
'setCurrentInvidiousInstance'
'setCurrentInvidiousInstance',
'setCurrentPipedInstance'
]),
...mapActions([
@ -266,7 +329,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"
@ -50,6 +51,15 @@
:icon="['fas', 'server']"
@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
:placeholder="$t('Settings.General Settings.Default Landing Page')"
:value="landingPage"
@ -102,54 +112,57 @@
@change="updateExternalLinkHandling"
/>
</div>
<div
v-if="backendPreference === 'invidious' || backendFallback"
<template
v-if="backendPreference === 'piped'"
>
<ft-flex-box class="settingsFlexStart460px">
<ft-input
:placeholder="$t('Settings.General Settings.Current Invidious Instance')"
:show-action-button="false"
:show-label="true"
:value="currentInvidiousInstance"
:data-list="invidiousInstancesList"
:tooltip="$t('Tooltips.General Settings.Invidious Instance')"
@input="handleInvidiousInstanceInput"
/>
</ft-flex-box>
<ft-flex-box>
<div>
<a
href="https://api.invidious.io"
>
{{ $t('Settings.General Settings.View all Invidious instance information') }}
</a>
</div>
</ft-flex-box>
<p
v-if="defaultInvidiousInstance !== ''"
class="center"
<ft-instance-selector
:backend-type="'piped'"
:placeholder="$t('Settings.General Settings.Current Piped Instance')"
:tooltip="$t('Tooltips.General Settings.Piped Instance')"
:instance-list="pipedInstancesList"
:current-instance="currentPipedInstance"
:default-instance="defaultPipedInstance"
@input="handlePipedInstanceInput"
@setDefaultInstance="handleSetDefaultPipedInstanceClick"
@clearDefaultInstance="handleClearDefaultPipedInstanceClick"
/>
<br
v-if="(backendFallback && fallbackPreference === 'invidious')"
>
{{ $t('Settings.General Settings.The currently set default instance is {instance}', { instance: defaultInvidiousInstance }) }}
</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="handleSetDefaultInstanceClick"
/>
<ft-button
:label="$t('Settings.General Settings.Clear Default Instance')"
@click="handleClearDefaultInstanceClick"
/>
</ft-flex-box>
</div>
</template>
<template
v-if="(backendFallback && fallbackPreference === 'invidious') || backendPreference == 'invidious'"
>
<ft-instance-selector
:backend-type="'invidious'"
:placeholder="$t('Settings.General Settings.Current Invidious Instance')"
:tooltip="$t('Tooltips.General Settings.Invidious Instance')"
:instance-list="invidiousInstancesList"
:current-instance="currentInvidiousInstance"
:default-instance="defaultInvidiousInstance"
@input="handleInvidiousInstanceInput"
@setDefaultInstance="handleSetDefaultInvidiousInstanceClick"
@clearDefaultInstance="handleClearDefaultInvidiousInstanceClick"
/>
<br
v-if="(backendFallback && fallbackPreference === 'piped')"
>
</template>
<template
v-if="backendFallback && fallbackPreference === 'piped'"
>
<ft-instance-selector
:backend-type="'piped'"
:placeholder="$t('Settings.General Settings.Current Piped Instance')"
:tooltip="$t('Tooltips.General Settings.Piped Instance')"
:instance-list="pipedInstancesList"
:current-instance="currentPipedInstance"
:default-instance="defaultPipedInstance"
@input="handlePipedInstanceInput"
@setDefaultInstance="handleSetDefaultPipedInstanceClick"
@clearDefaultInstance="handleClearDefaultPipedInstanceClick"
/>
</template>
</ft-settings-section>
</template>

View File

@ -62,7 +62,11 @@ 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
},
autoplayVideos: function () {

View File

@ -1,6 +1,7 @@
import { defineComponent, nextTick } from 'vue'
import { mapActions } from 'vuex'
import FtShareButton from '../ft-share-button/ft-share-button.vue'
import FtFlexBox from '../ft-flex-box/ft-flex-box.vue'
import FtIconButton from '../ft-icon-button/ft-icon-button.vue'
import FtInput from '../ft-input/ft-input.vue'
@ -65,7 +66,7 @@ export default defineComponent({
},
viewCount: {
type: Number,
required: true,
default: null
},
lastUpdated: {
type: String,
@ -142,6 +143,10 @@ export default defineComponent({
return this.$store.getters.getBackendPreference
},
fallbackPreference: function () {
return this.$store.getters.getFallbackPreference
},
hideViews: function () {
return this.$store.getters.getHideVideoViews
},
@ -178,24 +183,44 @@ export default defineComponent({
return require('../../assets/img/thumbnail_placeholder.svg')
}
let baseUrl = 'https://i.ytimg.com'
if (this.backendPreference === 'invidious') {
baseUrl = this.currentInvidiousInstance
} else if (typeof this.playlistThumbnail === 'string' && this.playlistThumbnail.length > 0) {
// Use playlist thumbnail provided by YT when available
if (this.backendPreference === 'local') {
return this.playlistThumbnail
}
let baseUrl = ''
let backendPreference = this.backendPreference
if (backendPreference === 'piped') {
if (this.infoSource === 'piped' || this.playlistThumbnail) {
return this.playlistThumbnail
} else {
backendPreference = this.fallbackPreference
}
}
if (!process.env.IS_ELECTRON || backendPreference === 'invidious') {
baseUrl = this.currentInvidiousInstance
} else {
baseUrl = 'https://i.ytimg.com'
}
let imageUrl = ''
switch (this.thumbnailPreference) {
case 'start':
return `${baseUrl}/vi/${this.firstVideoId}/mq1.jpg`
imageUrl = `${baseUrl}/vi/${this.firstVideoId}/mq1.jpg`
break
case 'middle':
return `${baseUrl}/vi/${this.firstVideoId}/mq2.jpg`
imageUrl = `${baseUrl}/vi/${this.firstVideoId}/mq2.jpg`
break
case 'end':
return `${baseUrl}/vi/${this.firstVideoId}/mq3.jpg`
imageUrl = `${baseUrl}/vi/${this.firstVideoId}/mq3.jpg`
break
default:
return `${baseUrl}/vi/${this.firstVideoId}/mqdefault.jpg`
imageUrl = `${baseUrl}/vi/${this.firstVideoId}/mqdefault.jpg`
}
return imageUrl
},
isUserPlaylist() {

View File

@ -48,11 +48,14 @@
</h2>
<p>
{{ $tc('Global.Counts.Video Count', videoCount, {count: parsedVideoCount}) }}
<span v-if="!hideViews && !isUserPlaylist">
<span v-if="infoSource !== 'piped'">
-
</span>
<span v-if="!hideViews && !isUserPlaylist && infoSource !== 'piped'">
- {{ $tc('Global.Counts.View Count', viewCount, {count: parsedViewCount}) }}
</span>
<span>- </span>
<span v-if="infoSource !== 'local'">
<span v-if="infoSource !== 'piped'">- </span>
<span v-if="infoSource !== 'local' && infoSource !== 'piped'">
{{ $t("Playlist.Last Updated On") }}
</span>
{{ lastUpdated }}
@ -67,6 +70,12 @@
:value="newDescription"
@input="(input) => newDescription = input"
/>
<p
v-if="infoSource === 'piped'"
class="playlistDescription"
v-html="description"
/>
<p
v-else
class="playlistDescription"

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

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

@ -14,11 +14,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

@ -21,11 +21,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

@ -22,11 +22,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

@ -20,11 +20,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

@ -22,11 +22,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

@ -72,12 +72,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

@ -6,7 +6,7 @@ import FtTimestampCatcher from '../../components/ft-timestamp-catcher/ft-timesta
import { copyToClipboard, showToast } from '../../helpers/utils'
import { invidiousGetCommentReplies, invidiousGetComments } from '../../helpers/api/invidious'
import { getLocalComments, parseLocalComment } from '../../helpers/api/local'
import { getPipedComments, getPipedCommentsMore } from '../../helpers/api/piped'
export default defineComponent({
name: 'WatchVideoComments',
components: {
@ -45,6 +45,7 @@ export default defineComponent({
nextPageToken: null,
commentData: [],
sortNewest: false,
apiUsed: 'local',
}
},
computed: {
@ -52,6 +53,10 @@ export default defineComponent({
return this.$store.getters.getBackendPreference
},
fallbackPreference: function () {
return this.$store.getters.getFallbackPreference
},
backendFallback: function () {
return this.$store.getters.getBackendFallback
},
@ -153,7 +158,9 @@ export default defineComponent({
getCommentData: function () {
this.isLoading = true
if (!process.env.SUPPORTS_LOCAL_API || this.backendPreference === 'invidious') {
if (this.backendPreference === 'piped') {
this.getCommentDataPiped()
} else if (!process.env.SUPPORTS_LOCAL_API || this.backendPreference === 'invidious') {
this.getCommentDataInvidious()
} else {
this.getCommentDataLocal()
@ -164,7 +171,9 @@ export default defineComponent({
if (this.commentData.length === 0 || this.nextPageToken === null || typeof this.nextPageToken === 'undefined') {
showToast(this.$t('Comments.There are no more comments for this video'))
} else {
if (!process.env.SUPPORTS_LOCAL_API || this.backendPreference === 'invidious') {
if (this.apiUsed === 'piped') {
this.getCommentDataPipedMore(this.nextPageToken)
} else if (!process.env.SUPPORTS_LOCAL_API || this.apiUsed === 'invidious') {
this.getCommentDataInvidious()
} else {
this.getCommentDataLocal(true)
@ -181,7 +190,9 @@ export default defineComponent({
},
getCommentReplies: function (index) {
if (process.env.SUPPORTS_LOCAL_API) {
if (this.commentData[index].dataType === 'piped') {
this.getCommentDataPipedMore(this.commentData[index].replyToken, index)
} else if (process.env.SUPPORTS_LOCAL_API) {
switch (this.commentData[index].dataType) {
case 'local':
this.getCommentRepliesLocal(index)
@ -217,6 +228,7 @@ export default defineComponent({
this.nextPageToken = comments.has_continuation ? comments : null
this.isLoading = false
this.showComments = true
this.apiUsed = 'local'
} catch (err) {
console.error(err)
const errorMessage = this.$t('Local API Error (Click to copy)')
@ -224,8 +236,13 @@ export default defineComponent({
copyToClipboard(err)
})
if (this.backendFallback && this.backendPreference === 'local') {
showToast(this.$t('Falling back to Invidious API'))
this.getCommentDataInvidious()
if (this.fallbackPreference === 'invidious') {
showToast(this.$t('Falling back to Invidious API'))
this.getCommentDataInvidious()
} else if (this.fallbackPreference === 'piped') {
showToast(this.$t('Falling back to Piped API'))
this.getCommentDataPiped()
}
} else {
this.isLoading = false
}
@ -257,8 +274,76 @@ export default defineComponent({
copyToClipboard(err)
})
if (this.backendFallback && this.backendPreference === 'local') {
showToast(this.$t('Falling back to Invidious API'))
this.getCommentDataInvidious()
if (this.fallbackPreference === 'invidious') {
showToast(this.$t('Falling back to Invidious API'))
this.getCommentDataInvidious()
} else if (this.fallbackPreference === 'piped') {
showToast(this.$t('Falling back to Piped API'))
this.getCommentDataPiped()
}
} else {
this.isLoading = false
}
}
},
getCommentDataPiped: async function () {
try {
const { comments, continuation } = await getPipedComments(this.id)
this.commentData = comments
this.nextPageToken = continuation
this.isLoading = false
this.showComments = true
this.apiUsed = 'piped'
} catch (err) {
console.error(err)
const errorMessage = this.$t('Piped API Error (Click to copy)')
showToast(`${errorMessage}: ${err}`, 10000, () => {
copyToClipboard(err)
})
if (this.backendFallback && this.backendPreference === 'piped') {
if (this.fallbackPreference === 'invidious') {
showToast(this.$t('Falling back to Invidious API'))
this.getCommentDataInvidious()
} else if (process.env.IS_ELECTRON && this.fallbackPreference === 'local') {
showToast(this.$t('Falling back to Local API'))
this.getCommentDataLocal()
}
} else {
this.isLoading = false
}
}
},
getCommentDataPipedMore: async function(token, index = null) {
try {
const { comments, continuation } = await getPipedCommentsMore({
videoId: this.id,
continuation: token
})
if (index !== null) {
this.commentData[index].replies = this.commentData[index].replies.concat(comments)
this.commentData[index].showReplies = true
this.commentData[index].replyToken = continuation
} else {
this.commentData = this.commentData.concat(comments)
this.nextPageToken = continuation
}
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.backendFallback && this.backendPreference === 'piped') {
if (this.fallbackPreference === 'invidious') {
showToast(this.$t('Falling back to Invidious API'))
this.getCommentDataInvidious()
} else if (process.env.IS_ELECTRON && this.fallbackPreference === 'local') {
showToast(this.$t('Falling back to Local API'))
this.getCommentDataLocal()
}
} else {
this.isLoading = false
}
@ -275,6 +360,7 @@ export default defineComponent({
this.nextPageToken = response.continuation
this.isLoading = false
this.showComments = true
this.apiUsed = 'invidious'
}).catch((err) => {
// region No comment detection
// No comment related info when video info requested earlier in parent component
@ -294,9 +380,15 @@ export default defineComponent({
showToast(`${errorMessage}: ${err}`, 10000, () => {
copyToClipboard(err)
})
if (process.env.SUPPORTS_LOCAL_API && this.backendFallback && this.backendPreference === 'invidious') {
showToast(this.$t('Falling back to Local API'))
this.getCommentDataLocal()
if (this.backendFallback && this.backendPreference === 'invidious') {
if (this.fallbackPreference === 'piped') {
showToast(this.$t('Falling back to Piped API'))
this.getCommentDataPiped()
} else if (process.env.SUPPORTS_LOCAL_API && this.fallbackPreference === 'local') {
showToast(this.$t('Falling back to Local API'))
this.getCommentDataLocal()
}
} else {
this.isLoading = false
}

View File

@ -61,11 +61,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

@ -10,6 +10,7 @@ import {
untilEndOfLocalPlayList,
} from '../../helpers/api/local'
import { invidiousGetPlaylistInfo } from '../../helpers/api/invidious'
import { getPipedPlaylist, getPipedPlaylistMore } from '../../helpers/api/piped'
export default defineComponent({
name: 'WatchVideoPlaylist',
@ -62,6 +63,10 @@ export default defineComponent({
return this.$store.getters.getBackendPreference
},
fallbackPreference: function () {
return this.$store.getters.getFallbackPreference
},
backendFallback: function () {
return this.$store.getters.getBackendFallback
},
@ -204,7 +209,9 @@ export default defineComponent({
},
playlistId: function (newVal, oldVal) {
if (oldVal !== newVal) {
if (!process.env.SUPPORTS_LOCAL_API || this.backendPreference === 'invidious') {
if (this.backendPreference === 'piped') {
this.getPlaylistInformationPiped()
} else if (!process.env.SUPPORTS_LOCAL_API || this.backendPreference === 'invidious') {
this.getPlaylistInformationInvidious()
} else {
this.getPlaylistInformationLocal()
@ -243,6 +250,8 @@ export default defineComponent({
if (this.selectedUserPlaylist != null) {
this.parseUserPlaylist(this.selectedUserPlaylist)
} else if (this.backendPreference === 'piped') {
this.getPlaylistInformationPiped()
} else if (!process.env.SUPPORTS_LOCAL_API || this.backendPreference === 'invidious') {
this.getPlaylistInformationInvidious()
} else {
@ -380,7 +389,26 @@ export default defineComponent({
this.channelName = cachedPlaylist.channelName
this.channelId = cachedPlaylist.channelId
if (!process.env.SUPPORTS_LOCAL_API || this.backendPreference === 'invidious' || cachedPlaylist.continuationData === null) {
if (this.backendPreference === 'piped') {
const items = cachedPlaylist.items
let nextpage = cachedPlaylist.continuationData
while (nextpage != null) {
const moreInfo = await getPipedPlaylistMore({
playlistId: cachedPlaylist.id,
continuation: nextpage
})
items.push(...moreInfo.videos)
if (!moreInfo.nextpage) {
nextpage = null
} else {
nextpage = moreInfo.nextpage
}
}
this.playlistItems = items
} else if (!process.env.SUPPORTS_LOCAL_API || this.backendPreference === 'invidious' || cachedPlaylist.continuationData === null) {
this.playlistItems = cachedPlaylist.items
} else {
const videos = cachedPlaylist.items
@ -430,8 +458,54 @@ 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 if (process.env.IS_ELECTRON && this.fallbackPreference === 'local') {
showToast(this.$t('Falling back to Local API'))
this.getPlaylistInformationLocal()
} else {
this.isLoading = false
}
} else {
this.isLoading = false
}
@ -456,9 +530,16 @@ export default defineComponent({
showToast(`${errorMessage}: ${err}`, 10000, () => {
copyToClipboard(err)
})
if (process.env.SUPPORTS_LOCAL_API && this.backendPreference === 'invidious' && this.backendFallback) {
showToast(this.$t('Falling back to Local API'))
this.getPlaylistInformationLocal()
if (this.backendPreference === 'invidious' && this.backendFallback) {
if (process.env.SUPPORTS_LOCAL_API && 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

@ -125,7 +125,7 @@
>
<ft-list-video-numbered
v-for="(item, index) in playlistItems"
:key="item.playlistItemId || item.videoId"
:key="item.playlistItemId || (item.videoId + index)"
:ref="currentVideoIndexZeroBased === index ? 'currentVideoItem' : null"
class="playlistItem"
:data="item"

View File

@ -0,0 +1,178 @@
import store from '../../store/index'
import { toLocalePublicationString } from '../utils'
import { isNullOrEmpty } from '../strings'
function getCurrentInstance() {
return store.getters.getCurrentPipedInstance
}
export async function pipedRequest({ resource, id = '', params = {}, doLogError = true, subResource = '' }) {
const requestUrl = getCurrentInstance() + '/' + resource + '/' + id + (!isNullOrEmpty(subResource) ? `/${subResource}` : '') + '?' + new URLSearchParams(params).toString()
return await fetch(requestUrl)
.then((response) => response.json())
.then((json) => {
if (json.error !== undefined) {
throw new Error(json.error)
}
return json
})
.catch((error) => {
if (doLogError) {
console.error('Piped API error', requestUrl, error)
}
return { isError: true, error }
})
}
export async function getPipedComments(videoId) {
const commentInfo = await pipedRequest({ resource: 'comments', id: videoId })
if (commentInfo.isError) {
throw commentInfo.error
} else {
commentInfo.comments = parsePipedComments(commentInfo.comments)
return {
comments: commentInfo.comments,
continuation: commentInfo.nextpage
}
}
}
export async function getPipedCommentsMore({ videoId, continuation }) {
const commentInfo = await pipedRequest({
resource: 'nextpage/comments',
id: videoId,
params: {
nextpage: continuation
}
})
if (commentInfo.isError) {
throw commentInfo.error
} else {
commentInfo.comments = parsePipedComments(commentInfo.comments)
return {
comments: commentInfo.comments,
continuation: commentInfo.nextpage
}
}
}
function parsePipedComments(comments) {
return comments.map(comment => {
const authorId = comment.commentorUrl.replace('/channel/', '')
return {
dataType: 'piped',
author: comment.author,
authorId: authorId,
authorLink: authorId,
authorThumb: comment.thumbnail,
commentId: comment.commentId,
text: comment.commentText,
isPinned: comment.pinned,
isVerified: comment.verified,
numReplies: comment.replyCount,
likes: comment.likeCount,
isHearted: comment.hearted,
replyToken: comment.repliesPage,
isMember: false,
isOwner: comment.channelOwner,
showReplies: false,
replies: [],
hasOwnerReplied: comment.creatorReplied,
time: toLocalePublicationString({
publishText: comment.commentedTime
})
}
})
}
export async function getPipedPlaylist(playlistId) {
const playlistInfo = await pipedRequest({ resource: 'playlists', id: playlistId })
if (playlistInfo.isError) {
throw playlistInfo.error
} else {
const parsedVideos = parsePipedVideos(playlistInfo.relatedStreams)
return {
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
}
})
if (playlistInfo.isError) {
throw playlistInfo.error
} else {
return {
nextpage: playlistInfo.nextpage,
videos: parsePipedVideos(playlistInfo.relatedStreams)
}
}
}
export function getPipedUrlInfo(url) {
const regex = /^(?<baseUrl>(https?:\/\/)[^/]*)\/((?<imageProtocol>vi|ytc)\/)?(?<resource>[^?]*).*host=(?<host>[^&]*)/
return url.match(regex)?.groups
}
export function pipedImageToYouTube(url) {
const urlInfo = getPipedUrlInfo(url)
let newUrl = `https://${urlInfo.host}/`
if (!isNullOrEmpty(urlInfo.imageProtocol)) {
newUrl += `${urlInfo.imageProtocol}/`
}
newUrl += urlInfo.resource
return newUrl
}
function parsePipedPlaylist(playlistId, result, parsedVideos) {
return {
id: playlistId,
title: result.name,
description: result.description,
firstVideoId: parsedVideos[0].videoId,
viewCount: null,
videoCount: result.videos,
channelName: result.uploader,
channelThumbnail: result.uploaderAvatar,
channelId: result.uploaderUrl.replace('/channel/', ''),
firstVideoThumbnail: parsedVideos[0].thumbnail,
thumbnailUrl: result.thumbnailUrl,
infoSource: 'piped'
}
}
function parsePipedVideos(videoList) {
return videoList.map(video => {
return {
videoId: video.url.replace('/watch?v=', ''),
title: video.title,
author: video.uploaderName,
authorId: video.uploaderUrl.replace('/channel/', ''),
lengthSeconds: video.duration,
description: video.shortDescription,
publishedText: video.uploadedDate, // uploaded time stamp
viewCount: video.views,
thumbnail: video.thumbnail,
isVerified: video.uploaderVerified,
type: 'video'
}
})
}
export async function getPipedTrending(region) {
const trendingInfo = await pipedRequest({ resource: 'trending', params: { region } })
if (trendingInfo.isError) {
throw trendingInfo.error
} else {
return parsePipedVideos(trendingInfo)
}
}

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,68 @@
import fs from 'fs/promises'
import { pathExists } from '../../helpers/filesystem'
import { fetchWithTimeout } from '../../helpers/utils'
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 fetchWithTimeout(15_000, 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

@ -167,6 +167,7 @@ const state = {
autoplayVideos: true,
backendFallback: process.env.SUPPORTS_LOCAL_API,
backendPreference: !process.env.SUPPORTS_LOCAL_API ? 'invidious' : 'local',
fallbackPreference: !process.env.SUPPORTS_LOCAL_API ? 'piped' : 'invidious',
barColor: false,
checkForBlogPosts: true,
checkForUpdates: true,
@ -399,6 +400,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

@ -143,11 +143,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

@ -31,11 +31,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'
},
showFetchMoreButton() {

View File

@ -21,6 +21,7 @@ import {
showToast,
} from '../../helpers/utils'
import { invidiousGetPlaylistInfo, youtubeImageUrlToInvidious } from '../../helpers/api/invidious'
import { getPipedPlaylist, getPipedPlaylistMore, pipedImageToYouTube } from '../../helpers/api/piped'
const SORT_BY_VALUES = {
DateAddedNewest: 'date_added_descending',
@ -88,6 +89,9 @@ export default defineComponent({
backendPreference: function () {
return this.$store.getters.getBackendPreference
},
fallbackPreference: function () {
return this.$store.getters.getFallbackPreference
},
backendFallback: function () {
return this.$store.getters.getBackendFallback
},
@ -308,6 +312,9 @@ export default defineComponent({
case 'invidious':
this.getPlaylistInvidious()
break
case 'piped':
this.getPlaylistPiped()
break
}
},
getPlaylistLocal: function () {
@ -356,8 +363,13 @@ export default defineComponent({
}).catch((err) => {
console.error(err)
if (this.backendPreference === 'local' && this.backendFallback) {
console.warn('Falling back to Invidious API')
this.getPlaylistInvidious()
if (this.fallbackPreference === 'invidious') {
console.warn('Falling back to Invidious API')
this.getPlaylistInvidious()
} else {
console.warn('Falling back to Piped API')
this.getPlaylistPiped()
}
} else {
this.isLoading = false
}
@ -392,9 +404,16 @@ export default defineComponent({
this.isLoading = false
}).catch((err) => {
console.error(err)
if (process.env.SUPPORTS_LOCAL_API && this.backendPreference === 'invidious' && this.backendFallback) {
console.warn('Error getting data with Invidious, falling back to local backend')
this.getPlaylistLocal()
if (this.backendPreference === 'invidious' && this.backendFallback) {
if (process.env.SUPPORTS_LOCAL_API && this.fallbackPreference === 'local') {
console.warn('Error getting data with Invidious, falling back to Local backend')
this.getPlaylistLocal()
} else if (this.fallbackPreference === 'piped') {
console.warn('Error getting data with Invidious, falling back to Piped backend')
this.getPlaylistPiped()
} else {
this.isLoading = false
}
} else {
this.isLoading = false
// TODO: Show toast with error message
@ -402,6 +421,48 @@ export default defineComponent({
})
},
getPlaylistPiped: async function () {
try {
this.isLoading = true
const { playlist, videos, nextpage } = await getPipedPlaylist(this.playlistId)
this.playlistTitle = playlist.title
this.playlistDescription = playlist.description
this.firstVideoId = videos.at(0)?.videoId
this.playlistThumbnail = playlist.thumbnailUrl
this.viewCount = playlist.viewCount
this.videoCount = playlist.videoCount
this.channelName = playlist.channelName
this.channelThumbnail = playlist.channelThumbnail
this.channelId = playlist.channelId
this.infoSource = 'piped'
this.continuationData = nextpage
this.playlistItems = videos
this.updateSubscriptionDetails({
channelThumbnailUrl: pipedImageToYouTube(playlist.channelThumbnail),
channelName: playlist.channelName,
channelId: playlist.channelId
})
this.isLoading = false
} catch (err) {
console.error(err)
if (this.backendPreference === 'piped' && this.backendFallback) {
if (process.env.IS_ELECTRON && this.fallbackPreference === 'local') {
console.warn('Error getting data with Piped, falling back to Local backend')
this.getPlaylistLocal()
} else if (this.fallbackPreference === 'invidious') {
console.warn('Error getting data with Piped, falling back to Invidious backend')
this.getPlaylistInvidious()
} else {
this.isLoading = false
}
} else {
this.isLoading = false
// TODO: Show toast with error message
}
}
},
parseUserPlaylist: function (playlist) {
this.playlistTitle = playlist.playlistName
this.playlistDescription = playlist.description ?? ''
@ -452,6 +513,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
}
},
@ -482,6 +546,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
},
moveVideoUp: function (videoId, playlistItemId) {
const playlistItems = [].concat(this.playlistItems)
const videoIndex = playlistItems.findIndex((video) => {

View File

@ -38,11 +38,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

@ -59,7 +59,11 @@ 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
},
currentInvidiousInstance: function () {

View File

@ -10,6 +10,7 @@ import FtRefreshWidget from '../../components/ft-refresh-widget/ft-refresh-widge
import { copyToClipboard, getRelativeTimeFromDate, setPublishedTimestampsInvidious, showToast } from '../../helpers/utils'
import { getLocalTrending } from '../../helpers/api/local'
import { invidiousAPICall } from '../../helpers/api/invidious'
import { getPipedTrending } from '../../helpers/api/piped'
export default defineComponent({
name: 'Trending',
@ -26,13 +27,17 @@ export default defineComponent({
isLoading: false,
shownResults: [],
currentTab: 'default',
trendingInstance: null
trendingInstance: null,
hideTabs: false
}
},
computed: {
backendPreference: function () {
return this.$store.getters.getBackendPreference
},
fallbackPreference: function() {
return this.$store.getters.getFallbackPreference
},
backendFallback: function () {
return this.$store.getters.getBackendFallback
},
@ -49,6 +54,8 @@ export default defineComponent({
mounted: function () {
document.addEventListener('keydown', this.keyboardShortcutHandler)
this.hideTabs = this.backendPreference === 'piped' && !this.backendFallback
if (this.trendingCache[this.currentTab] && this.trendingCache[this.currentTab].length > 0) {
this.getTrendingInfoCache()
} else {
@ -90,7 +97,9 @@ export default defineComponent({
this.$store.commit('clearTrendingCache')
}
if (!process.env.SUPPORTS_LOCAL_API || this.backendPreference === 'invidious') {
if (this.backendPreference === 'piped') {
this.getTrendingInfoPiped()
} else if (!process.env.SUPPORTS_LOCAL_API || this.backendPreference === 'invidious') {
this.getTrendingInfoInvidious()
} else {
this.getTrendingInfoLocal()
@ -119,9 +128,14 @@ export default defineComponent({
showToast(`${errorMessage}: ${err}`, 10000, () => {
copyToClipboard(err)
})
if (this.backendPreference === 'local' && this.backendFallback) {
showToast(this.$t('Falling back to Invidious API'))
this.getTrendingInfoInvidious()
if (this.backendFallback && this.backendPreference === 'local') {
if (this.fallbackPreference === 'invidious') {
showToast(this.$t('Falling back to Invidious API'))
this.getTrendingInfoInvidious()
} else if (this.fallbackPreference === 'piped') {
showToast(this.$t('Falling back to Piped API'))
this.getTrendingInfoPiped()
}
} else {
this.isLoading = false
}
@ -168,15 +182,58 @@ export default defineComponent({
copyToClipboard(err.responseText)
})
if (process.env.SUPPORTS_LOCAL_API && (this.backendPreference === 'invidious' && this.backendFallback)) {
showToast(this.$t('Falling back to Local API'))
this.getTrendingInfoLocal()
if (this.backendFallback && this.backendPreference === 'invidious') {
if (this.fallbackPreference === 'piped') {
showToast(this.$t('Falling back to Piped API'))
this.getTrendingInfoPiped()
} else if (process.env.SUPPORTS_LOCAL_API && this.fallbackPreference === 'local') {
showToast(this.$t('Falling back to Local API'))
this.getTrendingInfoLocal()
}
} else {
this.isLoading = false
}
})
},
getTrendingInfoPiped: async function() {
try {
this.hideTabs = this.backendPreference === 'piped' && !this.backendFallback
this.isLoading = true
if (this.currentTab === 'default') {
this.currentTab = 'default'
const returnData = await getPipedTrending(this.region)
this.shownResults = returnData
this.isLoading = false
this.$store.commit('setTrendingCache', { value: returnData, page: this.currentTab })
} else {
throw new Error('Trending Tabs are not supported by Piped')
}
} catch (err) {
if (err.message !== 'Trending Tabs are not supported by Piped' || this.backendPreference !== 'piped') {
console.error(err)
const errorMessage = this.$t('Piped API Error (Click to copy)')
showToast(`${errorMessage}: ${err}`, 10000, () => {
copyToClipboard(err)
})
} else {
console.error(err.message)
}
if (this.backendFallback && this.backendPreference === 'piped') {
if (this.fallbackPreference === 'invidious') {
showToast(this.$t('Falling back to Invidious API'))
this.getTrendingInfoInvidious()
} else if (process.env.IS_ELECTRON && this.fallbackPreference === 'local') {
showToast(this.$t('Falling back to Local API'))
this.getTrendingInfoLocal()
}
} else {
this.isLoading = false
}
}
},
/**
* This function `keyboardShortcutHandler` should always be at the bottom of this file
* @param {KeyboardEvent} event the keyboard event

View File

@ -10,6 +10,7 @@
>
<h2>{{ $t("Trending.Trending") }}</h2>
<ft-flex-box
v-if="!hideTabs"
class="trendingInfoTabs"
role="tablist"
:aria-label="$t('Trending.Trending Tabs')"
@ -81,7 +82,7 @@
</ft-flex-box>
<ft-element-list
id="trendingPanel"
role="tabpanel"
:role="!hideTabs ? 'tabPanel' : null"
:data="shownResults"
/>
</ft-card>

View File

@ -145,10 +145,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

@ -626,6 +626,7 @@ Settings:
Remove Password: Odebrat heslo
Set Password: Nastavit heslo
Expand All Settings Sections: Rozbalit všechny sekce nastavení
Sort Settings Sections (A-Z): Seřadit sekce nastavení (A-Z)
About:
#On About page
About: 'O aplikaci'

View File

@ -260,8 +260,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
@ -275,12 +277,14 @@ Settings:
Hidden: Hidden
Blur: Blur
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:
@ -955,16 +959,19 @@ Up Next: Up Next
#Tooltips
Tooltips:
General Settings:
Preferred API Backend: Choose the backend that FreeTube uses to obtain data. The
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 and FreeTube currently only supports: playlists, comments and trending from Piped."
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.
External Link Handling: |
@ -1022,7 +1029,9 @@ Tooltips:
# Toast Messages
Local API Error (Click to copy): Local API Error (Click to copy)
Invidious API Error (Click to copy): Invidious API Error (Click to copy)
Piped API Error (Click to copy): Piped 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 Local API: Falling back to 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
@ -1041,7 +1050,8 @@ Playlist will not pause when current video is finished: Playlist will not pause
Playlist will pause when current video is finished: Playlist will pause when current video is finished
Playing Next Video Interval: Playing next video in no time. Click to cancel. | Playing next video in {nextVideoInterval} second. Click to cancel. | Playing next video in {nextVideoInterval} seconds. Click to cancel.
Canceled next video autoplay: Canceled next video autoplay
Default Piped instance has been set to {instance}: Default Piped instance has been set to {instance}
Default Piped instance has been cleared: Default Piped instance has been cleared
Default Invidious instance has been set to {instance}: Default Invidious instance has been set to {instance}
Default Invidious instance has been cleared: Default Invidious instance has been cleared
'The playlist has ended. Enable loop to continue playing': 'The playlist has ended. Enable

View File

@ -639,6 +639,7 @@ Settings:
Password Incorrect: Contraseña incorrecta
Unlock: Desbloquear
Expand All Settings Sections: Expandir todas las secciones de los ajustes
Sort Settings Sections (A-Z): Ordenar secciones de configuración (A-Z)
About:
#On About page
About: 'Acerca de'

View File

@ -649,6 +649,7 @@ Settings:
Set Password: Jelszó megadása
Remove Password: Jelszó eltávolítása
Expand All Settings Sections: Beállítások kiterjesztése
Sort Settings Sections (A-Z): Beállítások rendezése (A-Z)
About:
#On About page
About: 'Névjegy'

View File

@ -646,6 +646,7 @@ Settings:
Impostazioni
Remove Password: Rimuovi password
Expand All Settings Sections: Espandi tutte le sezioni delle Impostazioni
Sort Settings Sections (A-Z): Ordina le sezioni delle Impostazioni (A-Z)
About:
#On About page
About: 'Informazioni'

View File

@ -641,6 +641,7 @@ Settings:
Password: Senha
Enter Password To Unlock: Digite a senha para desbloquear as configurações
Expand All Settings Sections: Expandir todas as seções de configurações
Sort Settings Sections (A-Z): Classificar seções de configurações (A-Z)
About:
#On About page
About: 'Sobre'
@ -1222,6 +1223,6 @@ Age Restricted:
checkmark:
Display Label: '{label}: {value}'
Feed:
Feed Last Updated: '{feedName} última atualização do feed: {date}'
Feed Last Updated: '{feedName} última atualização: {date}'
Refresh Feed: Atualizar {subscriptionName}
Moments Ago: momentos atrás

View File

@ -638,6 +638,7 @@ Settings:
Remove Password: Parolayı Kaldır
Set Password: Parola Ayarla
Expand All Settings Sections: Tüm Ayarlar Bölümlerini Genişlet
Sort Settings Sections (A-Z): Ayarlar Bölümlerini Sırala (A-Z)
About:
#On About page
About: 'Hakkında'

View File

@ -565,6 +565,7 @@ Settings:
Set Password: 设置密码
Remove Password: 删除密码
Expand All Settings Sections: 展开所有设置部分
Sort Settings Sections (A-Z): 对设置部分进行排序A-Z
About:
#On About page
About: '关于'

View File

@ -0,0 +1,22 @@
[
"https://pipedapi.kavin.rocks",
"https://pipedapi.syncpundit.io",
"https://piped-api.garudalinux.org",
"https://piped-api.lunar.icu",
"https://pipedapi.r4fo.com",
"https://pipedapi.darkness.services",
"https://pipedapi-libre.kavin.rocks",
"https://api.piped.projectsegfau.lt",
"https://pipedapi.in.projectsegfau.lt",
"https://pipedapi.us.projectsegfau.lt",
"https://pipedapi.smnz.de",
"https://pipedapi.adminforge.de",
"https://pipedapi.astartes.nl",
"https://pipedapi.drgns.space",
"https://piapi.ggtyler.dev",
"https://pipedapi.12a.app",
"https://pipedapi.ngn.tf",
"https://pipedapi.coldforge.xyz",
"https://piped-api.codespace.cz",
"https://pipedapi.reallyaweso.me"
]