mirror of https://github.com/FreeTubeApp/FreeTube
Compare commits
102 Commits
987fd26bb8
...
dff0e9ff75
Author | SHA1 | Date |
---|---|---|
ChunkyProgrammer | dff0e9ff75 | |
summoner001 | ec5ca2bb66 | |
gallegonovato | b7e94669cb | |
Fjuro | ce57393549 | |
Oğuz Ersen | f5c89f3f9f | |
Massimo Pissarello | a2d67523ab | |
Jose Delvani | fc123f3792 | |
大王叫我来巡山 | 1bd3ee936a | |
ChunkyProgrammer | aaefcd17db | |
ChunkyProgrammer | 4938335635 | |
ChunkyProgrammer | e427b81c49 | |
ChunkyProgrammer | 147600b698 | |
ChunkyProgrammer | ff715e9c88 | |
ChunkyProgrammer | 742a390fd8 | |
ChunkyProgrammer | f0733a7662 | |
ChunkyProgrammer | 0db77be01d | |
ChunkyProgrammer | e1f040a5f2 | |
ChunkyProgrammer | 8883f8a2a8 | |
ChunkyProgrammer | 58d3d7e48f | |
ChunkyProgrammer | ca588c77e2 | |
ChunkyProgrammer | 53ceba0d13 | |
ChunkyProgrammer | 469f9282ee | |
ChunkyProgrammer | f6ba3c9fc9 | |
ChunkyProgrammer | 8c75d8fbfb | |
ChunkyProgrammer | 9c8df73892 | |
ChunkyProgrammer | 40cc1d404b | |
ChunkyProgrammer | 6fc7cb0b44 | |
ChunkyProgrammer | 461b0f6632 | |
ChunkyProgrammer | a749d189b7 | |
ChunkyProgrammer | 86ef3b1c6a | |
ChunkyProgrammer | 2b2d677549 | |
ChunkyProgrammer | 1b17f6bc34 | |
ChunkyProgrammer | 4fa1d23e7c | |
ChunkyProgrammer | 44c5d2aa95 | |
ChunkyProgrammer | e445500cb5 | |
ChunkyProgrammer | 915a30d950 | |
ChunkyProgrammer | 99dac6dea6 | |
ChunkyProgrammer | dd18e516e1 | |
ChunkyProgrammer | 25846f8c02 | |
ChunkyProgrammer | 784f495a80 | |
ChunkyProgrammer | d4b2ac9329 | |
ChunkyProgrammer | b509adaac0 | |
ChunkyProgrammer | 2ae441dc28 | |
ChunkyProgrammer | 2151c1cd59 | |
ChunkyProgrammer | f9b791e8f6 | |
ChunkyProgrammer | ab6bcc023b | |
ChunkyProgrammer | fe4032bbf2 | |
ChunkyProgrammer | bb6f32127f | |
ChunkyProgrammer | a8998b9269 | |
ChunkyProgrammer | 2c50ca4b7a | |
ChunkyProgrammer | 533c9da3b7 | |
ChunkyProgrammer | 04eb9c6a1d | |
ChunkyProgrammer | a757a44773 | |
ChunkyProgrammer | 550a9abe5c | |
ChunkyProgrammer | 22be652c54 | |
ChunkyProgrammer | 6647e83d6b | |
ChunkyProgrammer | aa7f21c97f | |
ChunkyProgrammer | 863c1d2572 | |
ChunkyProgrammer | d530d71eab | |
ChunkyProgrammer | d9e208c555 | |
Chunky programmer | 36105af0f9 | |
Chunky programmer | 7370f689f9 | |
ChunkyProgrammer | 0e88d70987 | |
ChunkyProgrammer | e759679c81 | |
Chunky programmer | 59dfdb2857 | |
Chunky programmer | 0614bfad39 | |
Chunky programmer | 66578d25af | |
Chunky programmer | f58490aca5 | |
Chunky programmer | eb1bf9c75c | |
Chunky programmer | 3cafb80798 | |
Chunky programmer | 3300118ff8 | |
Chunky programmer | f69416c527 | |
Chunky programmer | 5f92e0732c | |
Chunky programmer | add031b79a | |
chunky programmer | 48601a64f1 | |
chunky programmer | 13d3acca1b | |
ChunkyProgrammer | 0801c07d0b | |
chunky programmer | 31704e0fba | |
chunky programmer | f109da2784 | |
chunky programmer | 632a7c8c24 | |
chunky programmer | a581bd04f3 | |
chunky programmer | 315e96bc4f | |
chunky programmer | 92a3bbdaa0 | |
chunky programmer | d5f9517073 | |
chunky programmer | 3b4ee5990f | |
chunky programmer | 3633a86ac3 | |
chunky programmer | 17c7510f90 | |
chunky programmer | 5ef7627195 | |
chunky programmer | b883d8328d | |
chunky programmer | c5244b0bd4 | |
ChunkyProgrammer | 44e82ac65f | |
chunky programmer | 34a1706aa5 | |
chunky programmer | 5491ef621d | |
chunky programmer | 7e5bb17973 | |
chunky programmer | 34f483cce5 | |
chunky programmer | b23256734d | |
chunky programmer | 379b593283 | |
chunky programmer | 7550f5983b | |
chunky programmer | 98fdaa3605 | |
chunky programmer | 1ffcc5584e | |
chunky programmer | beb3e5538f | |
chunky programmer | 5d903ed4c2 |
|
@ -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))
|
||||
})
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
}
|
||||
}
|
||||
})
|
|
@ -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>
|
|
@ -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 () {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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 () {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 () {
|
||||
|
|
|
@ -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 () {
|
||||
|
|
|
@ -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 () {
|
||||
|
|
|
@ -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 () {
|
||||
|
|
|
@ -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 () {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 () {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
import history from './history'
|
||||
import invidious from './invidious'
|
||||
import piped from './piped'
|
||||
import playlists from './playlists'
|
||||
import profiles from './profiles'
|
||||
import settings from './settings'
|
||||
|
@ -14,6 +15,7 @@ import utils from './utils'
|
|||
export default {
|
||||
history,
|
||||
invidious,
|
||||
piped,
|
||||
playlists,
|
||||
profiles,
|
||||
settings,
|
||||
|
|
|
@ -0,0 +1,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
|
||||
}
|
|
@ -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) => {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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 () {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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: '关于'
|
||||
|
|
|
@ -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"
|
||||
]
|
Loading…
Reference in New Issue