mirror of https://github.com/FreeTubeApp/FreeTube
Merge branch 'development' into piped-support
This commit is contained in:
commit
d9e208c555
|
@ -91,6 +91,7 @@ body:
|
|||
- .exe
|
||||
- Flathub
|
||||
- MPR
|
||||
- Nix
|
||||
- .pacman
|
||||
- Portable
|
||||
- PortableApps
|
||||
|
|
|
@ -17,7 +17,7 @@ jobs:
|
|||
"labels": ["B: visual"]
|
||||
},
|
||||
{
|
||||
"keywords": ["AUR", "Chocolatey", "FreeTubeCordova", "PortableApps", "winget", "Scoop", "Snapcraft", "MPR"],
|
||||
"keywords": ["AUR", "Chocolatey", "FreeTubeCordova", "PortableApps", "winget", "Scoop", "Snapcraft", "MPR", "Nix"],
|
||||
"labels": ["B: Unofficial Download"]
|
||||
},
|
||||
{
|
||||
|
|
|
@ -91,6 +91,8 @@ These builds are maintained by the community. While they should be safe, downloa
|
|||
|
||||
* makedeb Package Repository (MPR): [Download](https://mpr.makedeb.org/packages/freetube-bin)
|
||||
|
||||
* Nix Packages: [Download](https://search.nixos.org/packages?query=freetube)
|
||||
|
||||
* PortableApps (Windows Only): [Download](https://github.com/rddim/FreeTubePortable/releases) and [Source Code](https://github.com/rddim/FreeTubePortable)
|
||||
|
||||
* Scoop (Windows Only): [Usage](https://github.com/ScoopInstaller/Scoop)
|
||||
|
|
32
package.json
32
package.json
|
@ -66,7 +66,7 @@
|
|||
"nedb-promises": "^6.2.1",
|
||||
"path-browserify": "^1.0.1",
|
||||
"process": "^0.11.10",
|
||||
"video.js": "7.21.4",
|
||||
"video.js": "7.21.5",
|
||||
"videojs-contrib-quality-levels": "^3.0.0",
|
||||
"videojs-http-source-selector": "^1.1.6",
|
||||
"videojs-mobile-ui": "^0.8.0",
|
||||
|
@ -78,21 +78,21 @@
|
|||
"vue-router": "^3.6.5",
|
||||
"vue-tiny-slider": "^0.1.39",
|
||||
"vuex": "^3.6.2",
|
||||
"youtubei.js": "^5.2.0"
|
||||
"youtubei.js": "^5.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.22.5",
|
||||
"@babel/eslint-parser": "^7.22.5",
|
||||
"@babel/core": "^7.22.9",
|
||||
"@babel/eslint-parser": "^7.22.9",
|
||||
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
||||
"@babel/preset-env": "^7.22.5",
|
||||
"@babel/preset-env": "^7.22.9",
|
||||
"@double-great/stylelint-a11y": "^2.0.2",
|
||||
"babel-loader": "^9.1.2",
|
||||
"babel-loader": "^9.1.3",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"css-loader": "^6.8.1",
|
||||
"css-minimizer-webpack-plugin": "^5.0.1",
|
||||
"electron": "^22.3.15",
|
||||
"electron": "^22.3.18",
|
||||
"electron-builder": "^23.6.0",
|
||||
"eslint": "^8.44.0",
|
||||
"eslint": "^8.45.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-config-standard": "^17.1.0",
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
|
@ -100,31 +100,31 @@
|
|||
"eslint-plugin-n": "^16.0.1",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-unicorn": "^47.0.0",
|
||||
"eslint-plugin-unicorn": "^48.0.0",
|
||||
"eslint-plugin-vue": "^9.15.1",
|
||||
"eslint-plugin-vuejs-accessibility": "^2.1.0",
|
||||
"eslint-plugin-yml": "^1.8.0",
|
||||
"html-webpack-plugin": "^5.5.3",
|
||||
"js-yaml": "^4.1.0",
|
||||
"json-minimizer-webpack-plugin": "^4.0.0",
|
||||
"lefthook": "^1.4.3",
|
||||
"lefthook": "^1.4.6",
|
||||
"mini-css-extract-plugin": "^2.7.6",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"postcss": "^8.4.24",
|
||||
"postcss": "^8.4.26",
|
||||
"postcss-scss": "^4.0.6",
|
||||
"prettier": "^2.8.8",
|
||||
"rimraf": "^5.0.1",
|
||||
"sass": "^1.63.6",
|
||||
"sass": "^1.64.1",
|
||||
"sass-loader": "^13.3.2",
|
||||
"stylelint": "^14.16.1",
|
||||
"stylelint-config-sass-guidelines": "^9.0.1",
|
||||
"stylelint-config-standard": "^29.0.0",
|
||||
"stylelint": "^15.10.2",
|
||||
"stylelint-config-sass-guidelines": "^10.0.0",
|
||||
"stylelint-config-standard": "^34.0.0",
|
||||
"stylelint-high-performance-animation": "^1.8.0",
|
||||
"tree-kill": "1.2.2",
|
||||
"vue-devtools": "^5.1.4",
|
||||
"vue-eslint-parser": "^9.3.1",
|
||||
"vue-loader": "^15.10.0",
|
||||
"webpack": "^5.88.1",
|
||||
"webpack": "^5.88.2",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"webpack-dev-server": "^4.15.1",
|
||||
"yaml-eslint-parser": "^1.2.2"
|
||||
|
|
|
@ -71,14 +71,36 @@ export default defineComponent({
|
|||
hideChannelPlaylists: function() {
|
||||
return this.$store.getters.getHideChannelPlaylists
|
||||
},
|
||||
hideChannelPodcasts: function() {
|
||||
return this.$store.getters.getHideChannelPodcasts
|
||||
},
|
||||
hideChannelReleases: function() {
|
||||
return this.$store.getters.getHideChannelReleases
|
||||
},
|
||||
hideChannelCommunity: function() {
|
||||
return this.$store.getters.getHideChannelCommunity
|
||||
},
|
||||
hideSubscriptionsVideos: function() {
|
||||
return this.$store.getters.getHideSubscriptionsVideos
|
||||
},
|
||||
hideSubscriptionsShorts: function() {
|
||||
return this.$store.getters.getHideSubscriptionsShorts
|
||||
},
|
||||
hideSubscriptionsLive: function() {
|
||||
return this.$store.getters.getHideSubscriptionsLive
|
||||
},
|
||||
showDistractionFreeTitles: function () {
|
||||
return this.$store.getters.getShowDistractionFreeTitles
|
||||
},
|
||||
channelsHidden: function () {
|
||||
return JSON.parse(this.$store.getters.getChannelsHidden)
|
||||
},
|
||||
hideSubscriptionsLiveTooltip: function () {
|
||||
return this.$t('Tooltips.Distraction Free Settings.Hide Subscriptions Live', {
|
||||
appWideSetting: this.$t('Settings.Distraction Free Settings.Hide Live Streams'),
|
||||
subsection: this.$t('Settings.Distraction Free Settings.Sections.General'),
|
||||
settingsSection: this.$t('Settings.Distraction Free Settings.Distraction Free Settings')
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -117,7 +139,12 @@ export default defineComponent({
|
|||
'updateHideFeaturedChannels',
|
||||
'updateHideChannelShorts',
|
||||
'updateHideChannelPlaylists',
|
||||
'updateHideChannelCommunity'
|
||||
'updateHideChannelCommunity',
|
||||
'updateHideChannelPodcasts',
|
||||
'updateHideChannelReleases',
|
||||
'updateHideSubscriptionsVideos',
|
||||
'updateHideSubscriptionsShorts',
|
||||
'updateHideSubscriptionsLive'
|
||||
])
|
||||
}
|
||||
})
|
||||
|
|
|
@ -37,6 +37,37 @@
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<h4
|
||||
class="groupTitle"
|
||||
>
|
||||
{{ $t('Settings.Distraction Free Settings.Sections.Subscriptions Page') }}
|
||||
</h4>
|
||||
<div class="switchColumnGrid">
|
||||
<div class="switchColumn">
|
||||
<ft-toggle-switch
|
||||
:label="$t('Settings.Distraction Free Settings.Hide Subscriptions Videos')"
|
||||
:compact="true"
|
||||
:default-value="hideSubscriptionsVideos"
|
||||
@change="updateHideSubscriptionsVideos"
|
||||
/>
|
||||
<ft-toggle-switch
|
||||
:label="$t('Settings.Distraction Free Settings.Hide Subscriptions Shorts')"
|
||||
:compact="true"
|
||||
:default-value="hideSubscriptionsShorts"
|
||||
@change="updateHideSubscriptionsShorts"
|
||||
/>
|
||||
</div>
|
||||
<div class="switchColumn">
|
||||
<ft-toggle-switch
|
||||
:label="$t('Settings.Distraction Free Settings.Hide Subscriptions Live')"
|
||||
:compact="true"
|
||||
:disabled="hideLiveStreams"
|
||||
:default-value="hideLiveStreams || hideSubscriptionsLive"
|
||||
:tooltip="hideLiveStreams ? hideSubscriptionsLiveTooltip : ''"
|
||||
v-on="!hideLiveStreams ? { change: updateHideSubscriptionsLive } : {}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<h4
|
||||
class="groupTitle"
|
||||
>
|
||||
|
@ -56,6 +87,12 @@
|
|||
:default-value="hideChannelPlaylists"
|
||||
@change="updateHideChannelPlaylists"
|
||||
/>
|
||||
<ft-toggle-switch
|
||||
:label="$t('Settings.Distraction Free Settings.Hide Channel Podcasts')"
|
||||
:compact="true"
|
||||
:default-value="hideChannelPodcasts"
|
||||
@change="updateHideChannelPodcasts"
|
||||
/>
|
||||
</div>
|
||||
<div class="switchColumn">
|
||||
<ft-toggle-switch
|
||||
|
@ -70,6 +107,12 @@
|
|||
:default-value="hideFeaturedChannels"
|
||||
@change="updateHideFeaturedChannels"
|
||||
/>
|
||||
<ft-toggle-switch
|
||||
:label="$t('Settings.Distraction Free Settings.Hide Channel Releases')"
|
||||
:compact="true"
|
||||
:default-value="hideChannelReleases"
|
||||
@change="updateHideChannelReleases"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<h4
|
||||
|
|
|
@ -111,7 +111,8 @@ export default defineComponent({
|
|||
return Number.parseInt(b.width) - Number.parseInt(a.width)
|
||||
})
|
||||
|
||||
return imageArrayCopy.at(0)?.url ?? ''
|
||||
// Remove cropping directives when applicable
|
||||
return imageArrayCopy.at(0)?.url?.replace(/-c-fcrop64=.*/i, '') ?? ''
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -21,7 +21,11 @@ export default defineComponent({
|
|||
showVideoWithLastViewedPlaylist: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
useChannelsHiddenPreference: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
listType: function () {
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
:first-screen="index < 16"
|
||||
:layout="displayValue"
|
||||
:show-video-with-last-viewed-playlist="showVideoWithLastViewedPlaylist"
|
||||
:use-channels-hidden-preference="useChannelsHiddenPreference"
|
||||
/>
|
||||
</ft-auto-grid>
|
||||
</template>
|
||||
|
|
|
@ -33,6 +33,10 @@ export default defineComponent({
|
|||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
useChannelsHiddenPreference: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
|
@ -44,6 +48,9 @@ export default defineComponent({
|
|||
return this.$store.getters.getHideLiveStreams
|
||||
},
|
||||
channelsHidden: function() {
|
||||
// Some component users like channel view will have this disabled
|
||||
if (!this.useChannelsHiddenPreference) { return [] }
|
||||
|
||||
return JSON.parse(this.$store.getters.getChannelsHidden)
|
||||
},
|
||||
hideUpcomingPremieres: function () {
|
||||
|
@ -59,7 +66,7 @@ export default defineComponent({
|
|||
if (!data.type) {
|
||||
return false
|
||||
}
|
||||
if (data.type === 'video') {
|
||||
if (data.type === 'video' || data.type === 'shortVideo') {
|
||||
if (this.hideLiveStreams && (data.liveNow || data.lengthSeconds == null)) {
|
||||
// hide livestreams
|
||||
return false
|
||||
|
@ -67,6 +74,10 @@ export default defineComponent({
|
|||
if (this.hideUpcomingPremieres &&
|
||||
// Observed for premieres in Local API Channels.
|
||||
(data.premiereDate != null ||
|
||||
// Invidious API
|
||||
// `premiereTimestamp` only available on premiered videos
|
||||
// https://docs.invidious.io/api/common_types/#videoobject
|
||||
data.premiereTimestamp != null ||
|
||||
// viewCount is our only method of detecting premieres in RSS
|
||||
// data without sending an additional request.
|
||||
// If we ever get a better flag, use it here instead.
|
||||
|
@ -79,12 +90,30 @@ export default defineComponent({
|
|||
return false
|
||||
}
|
||||
} else if (data.type === 'channel') {
|
||||
if (this.channelsHidden.includes(data.channelID) || this.channelsHidden.includes(data.name)) {
|
||||
const attrsToCheck = [
|
||||
// Local API
|
||||
data.id,
|
||||
data.name,
|
||||
// Invidious API
|
||||
// https://docs.invidious.io/api/common_types/#channelobject
|
||||
data.author,
|
||||
data.authorId,
|
||||
]
|
||||
if (attrsToCheck.some(a => a != null && this.channelsHidden.includes(a))) {
|
||||
// hide channels by author
|
||||
return false
|
||||
}
|
||||
} else if (data.type === 'playlist') {
|
||||
if (this.channelsHidden.includes(data.authorId) || this.channelsHidden.includes(data.author)) {
|
||||
const attrsToCheck = [
|
||||
// Local API
|
||||
data.channelId,
|
||||
data.channelName,
|
||||
// Invidious API
|
||||
// https://docs.invidious.io/api/common_types/#playlistobject
|
||||
data.author,
|
||||
data.authorId,
|
||||
]
|
||||
if (attrsToCheck.some(a => a != null && this.channelsHidden.includes(a))) {
|
||||
// hide playlists by author
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -43,18 +43,35 @@ export default defineComponent({
|
|||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
useChannelsHiddenPreference: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
visible: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
channelsHidden() {
|
||||
// Some component users like channel view will have this disabled
|
||||
if (!this.useChannelsHiddenPreference) { return [] }
|
||||
|
||||
return JSON.parse(this.$store.getters.getChannelsHidden)
|
||||
},
|
||||
|
||||
shouldBeVisible() {
|
||||
return !(this.channelsHidden.includes(this.data.authorId) ||
|
||||
this.channelsHidden.includes(this.data.author))
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.visible = this.initialVisibleState
|
||||
},
|
||||
methods: {
|
||||
onVisibilityChanged: function (visible) {
|
||||
if (visible) {
|
||||
if (visible && this.shouldBeVisible) {
|
||||
this.visible = visible
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,12 @@ import { IpcChannels } from '../../../constants'
|
|||
import { sponsorBlockSkipSegments } from '../../helpers/sponsorblock'
|
||||
import { calculateColorLuminance, colors } from '../../helpers/colors'
|
||||
import { pathExists } from '../../helpers/filesystem'
|
||||
import { getPicturesPath, showSaveDialog, showToast } from '../../helpers/utils'
|
||||
import {
|
||||
copyToClipboard,
|
||||
getPicturesPath,
|
||||
showSaveDialog,
|
||||
showToast,
|
||||
} from '../../helpers/utils'
|
||||
import { getProxyUrl } from '../../helpers/api/invidious'
|
||||
import store from '../../store'
|
||||
|
||||
|
@ -367,7 +372,7 @@ export default defineComponent({
|
|||
this.determineFormatType()
|
||||
|
||||
if ('mediaSession' in navigator) {
|
||||
navigator.mediaSession.setActionHandler('play', () => this.player.play())
|
||||
navigator.mediaSession.setActionHandler('play', () => this.playVideo())
|
||||
navigator.mediaSession.setActionHandler('pause', () => this.player.pause())
|
||||
}
|
||||
|
||||
|
@ -531,7 +536,8 @@ export default defineComponent({
|
|||
if (this.autoplayVideos) {
|
||||
// Calling play() won't happen right away, so a quick timeout will make it function properly.
|
||||
setTimeout(() => {
|
||||
this.player.play()
|
||||
// `this.player` can be destroyed before this runs
|
||||
this.playVideo()
|
||||
}, 200)
|
||||
}
|
||||
|
||||
|
@ -869,7 +875,7 @@ export default defineComponent({
|
|||
this.player.playbackRate(this.defaultPlayback)
|
||||
} else {
|
||||
if (this.player.paused() || !this.player.hasStarted()) {
|
||||
this.player.play()
|
||||
this.playVideo()
|
||||
} else {
|
||||
this.player.pause()
|
||||
}
|
||||
|
@ -908,7 +914,7 @@ export default defineComponent({
|
|||
this.player.playbackRate(playbackRate)
|
||||
|
||||
// need to call play to restore the player state, even if we want to pause afterwards
|
||||
this.player.play().then(() => {
|
||||
this.playVideo(() => {
|
||||
if (isPaused) { this.player.pause() }
|
||||
})
|
||||
})
|
||||
|
@ -1248,7 +1254,7 @@ export default defineComponent({
|
|||
|
||||
togglePlayPause: function () {
|
||||
if (this.player.paused()) {
|
||||
this.player.play()
|
||||
this.playVideo()
|
||||
} else {
|
||||
this.player.pause()
|
||||
}
|
||||
|
@ -1571,7 +1577,7 @@ export default defineComponent({
|
|||
|
||||
const response = await showSaveDialog(options)
|
||||
if (wasPlaying) {
|
||||
this.player.play()
|
||||
this.playVideo()
|
||||
}
|
||||
if (response.canceled || response.filePath === '') {
|
||||
canvas.remove()
|
||||
|
@ -1950,6 +1956,34 @@ export default defineComponent({
|
|||
(process.platform === 'darwin' && event.metaKey))
|
||||
},
|
||||
|
||||
playVideo(thenFunc = null) {
|
||||
// It can be called in `setTimeout` & user can navigate to other pages before it runs
|
||||
// Which makes `this.player` become `null`
|
||||
if (this.player == null) { return }
|
||||
|
||||
let promise = this.player.play()
|
||||
if (typeof thenFunc === 'function') {
|
||||
promise = promise.then(thenFunc)
|
||||
}
|
||||
promise
|
||||
.catch(err => {
|
||||
if (err.message.includes('The play() request was interrupted by a new load request.')) {
|
||||
// Ignoring expected exception
|
||||
// This is thrown when `play()` called but user already viewing another page
|
||||
// console.debug('Ignoring expected error')
|
||||
// console.debug(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Unexpected errors should be reported
|
||||
console.error(err)
|
||||
const errorMessage = this.$t('play() request Error (Click to copy)')
|
||||
showToast(`${errorMessage}: ${err}`, 10000, () => {
|
||||
copyToClipboard(err)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
// This function should always be at the bottom of this file
|
||||
/**
|
||||
* @param {KeyboardEvent} event
|
||||
|
|
|
@ -190,7 +190,11 @@ export default defineComponent({
|
|||
},
|
||||
methods: {
|
||||
handleInvidiousInstanceInput: function (input) {
|
||||
const instance = input.replace(/\/$/, '')
|
||||
let instance = input
|
||||
// If NOT something like https:// (1-2 slashes), remove trailing slash
|
||||
if (!/^(https?):(\/){1,2}$/.test(input)) {
|
||||
instance = input.replace(/\/$/, '')
|
||||
}
|
||||
this.setCurrentInvidiousInstanceBounce(instance)
|
||||
},
|
||||
|
||||
|
|
|
@ -33,8 +33,9 @@ export default defineComponent({
|
|||
return this.$store.getters.getActiveProfile
|
||||
},
|
||||
activeSubscriptions: function () {
|
||||
const profile = JSON.parse(JSON.stringify(this.activeProfile))
|
||||
return profile.subscriptions.sort((a, b) => {
|
||||
const subscriptions = JSON.parse(JSON.stringify(this.activeProfile.subscriptions))
|
||||
|
||||
subscriptions.sort((a, b) => {
|
||||
const nameA = a.name.toLowerCase()
|
||||
const nameB = b.name.toLowerCase()
|
||||
if (nameA < nameB) {
|
||||
|
@ -44,13 +45,15 @@ export default defineComponent({
|
|||
return 1
|
||||
}
|
||||
return 0
|
||||
}).map((channel) => {
|
||||
if (this.backendPreference === 'invidious') {
|
||||
channel.thumbnail = youtubeImageUrlToInvidious(channel.thumbnail, this.currentInvidiousInstance)
|
||||
}
|
||||
|
||||
return channel
|
||||
})
|
||||
|
||||
if (this.backendPreference === 'invidious') {
|
||||
subscriptions.forEach((channel) => {
|
||||
channel.thumbnail = youtubeImageUrlToInvidious(channel.thumbnail, this.currentInvidiousInstance)
|
||||
})
|
||||
}
|
||||
|
||||
return subscriptions
|
||||
},
|
||||
hidePopularVideos: function () {
|
||||
return this.$store.getters.getHidePopularVideos
|
||||
|
|
|
@ -0,0 +1,335 @@
|
|||
import { defineComponent } from 'vue'
|
||||
import { mapActions, mapMutations } from 'vuex'
|
||||
import SubscriptionsTabUI from '../subscriptions-tab-ui/subscriptions-tab-ui.vue'
|
||||
|
||||
import { copyToClipboard, showToast } from '../../helpers/utils'
|
||||
import { invidiousAPICall } from '../../helpers/api/invidious'
|
||||
import { getLocalChannelLiveStreams } from '../../helpers/api/local'
|
||||
import { addPublishedDatesInvidious, addPublishedDatesLocal, parseYouTubeRSSFeed, updateVideoListAfterProcessing } from '../../helpers/subscriptions'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SubscriptionsLive',
|
||||
components: {
|
||||
'subscriptions-tab-ui': SubscriptionsTabUI
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
isLoading: false,
|
||||
videoList: [],
|
||||
errorChannels: [],
|
||||
attemptedFetch: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
backendPreference: function () {
|
||||
return this.$store.getters.getBackendPreference
|
||||
},
|
||||
|
||||
backendFallback: function () {
|
||||
return this.$store.getters.getBackendFallback
|
||||
},
|
||||
|
||||
currentInvidiousInstance: function () {
|
||||
return this.$store.getters.getCurrentInvidiousInstance
|
||||
},
|
||||
|
||||
useRssFeeds: function () {
|
||||
return this.$store.getters.getUseRssFeeds
|
||||
},
|
||||
|
||||
activeProfile: function () {
|
||||
return this.$store.getters.getActiveProfile
|
||||
},
|
||||
activeProfileId: function () {
|
||||
return this.activeProfile._id
|
||||
},
|
||||
|
||||
cacheEntriesForAllActiveProfileChannels() {
|
||||
const entries = []
|
||||
this.activeSubscriptionList.forEach((channel) => {
|
||||
const cacheEntry = this.$store.getters.getLiveCacheByChannel(channel.id)
|
||||
if (cacheEntry == null) { return }
|
||||
|
||||
entries.push(cacheEntry)
|
||||
})
|
||||
return entries
|
||||
},
|
||||
videoCacheForAllActiveProfileChannelsPresent() {
|
||||
if (this.cacheEntriesForAllActiveProfileChannels.length === 0) { return false }
|
||||
if (this.cacheEntriesForAllActiveProfileChannels.length < this.activeSubscriptionList.length) { return false }
|
||||
|
||||
return this.cacheEntriesForAllActiveProfileChannels.every((cacheEntry) => {
|
||||
return cacheEntry.videos != null
|
||||
})
|
||||
},
|
||||
|
||||
activeSubscriptionList: function () {
|
||||
return this.activeProfile.subscriptions
|
||||
},
|
||||
|
||||
fetchSubscriptionsAutomatically: function() {
|
||||
return this.$store.getters.getFetchSubscriptionsAutomatically
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
activeProfile: async function (_) {
|
||||
this.isLoading = true
|
||||
this.loadVideosFromCacheSometimes()
|
||||
},
|
||||
},
|
||||
mounted: async function () {
|
||||
this.isLoading = true
|
||||
|
||||
this.loadVideosFromCacheSometimes()
|
||||
},
|
||||
methods: {
|
||||
loadVideosFromCacheSometimes() {
|
||||
// This method is called on view visible
|
||||
if (this.videoCacheForAllActiveProfileChannelsPresent) {
|
||||
this.loadVideosFromCacheForAllActiveProfileChannels()
|
||||
return
|
||||
}
|
||||
|
||||
this.maybeLoadVideosForSubscriptionsFromRemote()
|
||||
},
|
||||
|
||||
async loadVideosFromCacheForAllActiveProfileChannels() {
|
||||
const videoList = []
|
||||
this.activeSubscriptionList.forEach((channel) => {
|
||||
const channelCacheEntry = this.$store.getters.getLiveCacheByChannel(channel.id)
|
||||
|
||||
videoList.push(...channelCacheEntry.videos)
|
||||
})
|
||||
this.videoList = updateVideoListAfterProcessing(videoList)
|
||||
this.isLoading = false
|
||||
},
|
||||
|
||||
loadVideosForSubscriptionsFromRemote: async function () {
|
||||
if (this.activeSubscriptionList.length === 0) {
|
||||
this.isLoading = false
|
||||
this.videoList = []
|
||||
return
|
||||
}
|
||||
|
||||
const channelsToLoadFromRemote = this.activeSubscriptionList
|
||||
const videoList = []
|
||||
let channelCount = 0
|
||||
this.isLoading = true
|
||||
|
||||
let useRss = this.useRssFeeds
|
||||
if (channelsToLoadFromRemote.length >= 125 && !useRss) {
|
||||
showToast(
|
||||
this.$t('Subscriptions["This profile has a large number of subscriptions. Forcing RSS to avoid rate limiting"]'),
|
||||
10000
|
||||
)
|
||||
useRss = true
|
||||
}
|
||||
this.updateShowProgressBar(true)
|
||||
this.setProgressBarPercentage(0)
|
||||
this.attemptedFetch = true
|
||||
|
||||
this.errorChannels = []
|
||||
const videoListFromRemote = (await Promise.all(channelsToLoadFromRemote.map(async (channel) => {
|
||||
let videos = []
|
||||
if (!process.env.IS_ELECTRON || this.backendPreference === 'invidious') {
|
||||
if (useRss) {
|
||||
videos = await this.getChannelLiveInvidiousRSS(channel)
|
||||
} else {
|
||||
videos = await this.getChannelLiveInvidious(channel)
|
||||
}
|
||||
} else {
|
||||
if (useRss) {
|
||||
videos = await this.getChannelLiveLocalRSS(channel)
|
||||
} else {
|
||||
videos = await this.getChannelLiveLocal(channel)
|
||||
}
|
||||
}
|
||||
|
||||
channelCount++
|
||||
const percentageComplete = (channelCount / channelsToLoadFromRemote.length) * 100
|
||||
this.setProgressBarPercentage(percentageComplete)
|
||||
this.updateSubscriptionLiveCacheByChannel({
|
||||
channelId: channel.id,
|
||||
videos: videos,
|
||||
})
|
||||
return videos
|
||||
}))).flatMap((o) => o)
|
||||
videoList.push(...videoListFromRemote)
|
||||
|
||||
this.videoList = updateVideoListAfterProcessing(videoList)
|
||||
this.isLoading = false
|
||||
this.updateShowProgressBar(false)
|
||||
},
|
||||
|
||||
maybeLoadVideosForSubscriptionsFromRemote: async function () {
|
||||
if (this.fetchSubscriptionsAutomatically) {
|
||||
// `this.isLoading = false` is called inside `loadVideosForSubscriptionsFromRemote` when needed
|
||||
await this.loadVideosForSubscriptionsFromRemote()
|
||||
} else {
|
||||
this.videoList = []
|
||||
this.attemptedFetch = false
|
||||
this.isLoading = false
|
||||
}
|
||||
},
|
||||
|
||||
getChannelLiveLocal: async function (channel, failedAttempts = 0) {
|
||||
try {
|
||||
const entries = await getLocalChannelLiveStreams(channel.id)
|
||||
|
||||
if (entries === null) {
|
||||
this.errorChannels.push(channel)
|
||||
return []
|
||||
}
|
||||
|
||||
addPublishedDatesLocal(entries)
|
||||
|
||||
return entries
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
const errorMessage = this.$t('Local API Error (Click to copy)')
|
||||
showToast(`${errorMessage}: ${err}`, 10000, () => {
|
||||
copyToClipboard(err)
|
||||
})
|
||||
switch (failedAttempts) {
|
||||
case 0:
|
||||
return await this.getChannelLiveLocalRSS(channel, failedAttempts + 1)
|
||||
case 1:
|
||||
if (this.backendFallback) {
|
||||
showToast(this.$t('Falling back to Invidious API'))
|
||||
return await this.getChannelLiveInvidious(channel, failedAttempts + 1)
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
case 2:
|
||||
return await this.getChannelLiveLocalRSS(channel, failedAttempts + 1)
|
||||
default:
|
||||
return []
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getChannelLiveLocalRSS: async function (channel, failedAttempts = 0) {
|
||||
const playlistId = channel.id.replace('UC', 'UULV')
|
||||
const feedUrl = `https://www.youtube.com/feeds/videos.xml?playlist_id=${playlistId}`
|
||||
|
||||
try {
|
||||
const response = await fetch(feedUrl)
|
||||
|
||||
if (response.status === 404) {
|
||||
return []
|
||||
}
|
||||
|
||||
return await parseYouTubeRSSFeed(await response.text(), channel.id)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
const errorMessage = this.$t('Local API Error (Click to copy)')
|
||||
showToast(`${errorMessage}: ${error}`, 10000, () => {
|
||||
copyToClipboard(error)
|
||||
})
|
||||
switch (failedAttempts) {
|
||||
case 0:
|
||||
return this.getChannelLiveLocal(channel, failedAttempts + 1)
|
||||
case 1:
|
||||
if (this.backendFallback) {
|
||||
showToast(this.$t('Falling back to Invidious API'))
|
||||
return this.getChannelLiveInvidiousRSS(channel, failedAttempts + 1)
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
case 2:
|
||||
return this.getChannelLiveLocal(channel, failedAttempts + 1)
|
||||
default:
|
||||
return []
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getChannelLiveInvidious: function (channel, failedAttempts = 0) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const subscriptionsPayload = {
|
||||
resource: 'channels',
|
||||
id: channel.id,
|
||||
subResource: 'streams',
|
||||
params: {}
|
||||
}
|
||||
|
||||
invidiousAPICall(subscriptionsPayload).then((result) => {
|
||||
const videos = result.videos.filter(e => e.type === 'video')
|
||||
|
||||
addPublishedDatesInvidious(videos)
|
||||
|
||||
resolve(videos)
|
||||
}).catch((err) => {
|
||||
console.error(err)
|
||||
const errorMessage = this.$t('Invidious API Error (Click to copy)')
|
||||
showToast(`${errorMessage}: ${err.responseText}`, 10000, () => {
|
||||
copyToClipboard(err.responseText)
|
||||
})
|
||||
switch (failedAttempts) {
|
||||
case 0:
|
||||
resolve(this.getChannelLiveInvidiousRSS(channel, failedAttempts + 1))
|
||||
break
|
||||
case 1:
|
||||
if (process.env.IS_ELECTRON && this.backendFallback) {
|
||||
showToast(this.$t('Falling back to the local API'))
|
||||
resolve(this.getChannelLiveLocal(channel, failedAttempts + 1))
|
||||
} else {
|
||||
resolve([])
|
||||
}
|
||||
break
|
||||
case 2:
|
||||
resolve(this.getChannelLiveInvidiousRSS(channel, failedAttempts + 1))
|
||||
break
|
||||
default:
|
||||
resolve([])
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
getChannelLiveInvidiousRSS: async function (channel, failedAttempts = 0) {
|
||||
const playlistId = channel.id.replace('UC', 'UULV')
|
||||
const feedUrl = `${this.currentInvidiousInstance}/feed/playlist/${playlistId}`
|
||||
|
||||
try {
|
||||
const response = await fetch(feedUrl)
|
||||
|
||||
if (response.status === 500) {
|
||||
return []
|
||||
}
|
||||
|
||||
return await parseYouTubeRSSFeed(await response.text(), channel.id)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
const errorMessage = this.$t('Invidious API Error (Click to copy)')
|
||||
showToast(`${errorMessage}: ${error}`, 10000, () => {
|
||||
copyToClipboard(error)
|
||||
})
|
||||
switch (failedAttempts) {
|
||||
case 0:
|
||||
return this.getChannelLiveInvidious(channel, failedAttempts + 1)
|
||||
case 1:
|
||||
if (process.env.IS_ELECTRON && this.backendFallback) {
|
||||
showToast(this.$t('Falling back to the local API'))
|
||||
return this.getChannelLiveLocalRSS(channel, failedAttempts + 1)
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
case 2:
|
||||
return this.getChannelLiveInvidious(channel, failedAttempts + 1)
|
||||
default:
|
||||
return []
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
...mapActions([
|
||||
'updateShowProgressBar',
|
||||
'updateSubscriptionLiveCacheByChannel',
|
||||
]),
|
||||
|
||||
...mapMutations([
|
||||
'setProgressBarPercentage'
|
||||
])
|
||||
}
|
||||
})
|
|
@ -0,0 +1,11 @@
|
|||
<template>
|
||||
<subscriptions-tab-ui
|
||||
:is-loading="isLoading"
|
||||
:video-list="videoList"
|
||||
:error-channels="errorChannels"
|
||||
:attempted-fetch="attemptedFetch"
|
||||
@refresh="loadVideosForSubscriptionsFromRemote"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script src="./subscriptions-live.js" />
|
|
@ -0,0 +1,225 @@
|
|||
import { defineComponent } from 'vue'
|
||||
import { mapActions, mapMutations } from 'vuex'
|
||||
import SubscriptionsTabUI from '../subscriptions-tab-ui/subscriptions-tab-ui.vue'
|
||||
|
||||
import { parseYouTubeRSSFeed, updateVideoListAfterProcessing } from '../../helpers/subscriptions'
|
||||
import { copyToClipboard, showToast } from '../../helpers/utils'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SubscriptionsShorts',
|
||||
components: {
|
||||
'subscriptions-tab-ui': SubscriptionsTabUI
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
isLoading: false,
|
||||
videoList: [],
|
||||
errorChannels: [],
|
||||
attemptedFetch: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
backendPreference: function () {
|
||||
return this.$store.getters.getBackendPreference
|
||||
},
|
||||
|
||||
backendFallback: function () {
|
||||
return this.$store.getters.getBackendFallback
|
||||
},
|
||||
|
||||
currentInvidiousInstance: function () {
|
||||
return this.$store.getters.getCurrentInvidiousInstance
|
||||
},
|
||||
|
||||
activeProfile: function () {
|
||||
return this.$store.getters.getActiveProfile
|
||||
},
|
||||
activeProfileId: function () {
|
||||
return this.activeProfile._id
|
||||
},
|
||||
|
||||
cacheEntriesForAllActiveProfileChannels() {
|
||||
const entries = []
|
||||
this.activeSubscriptionList.forEach((channel) => {
|
||||
const cacheEntry = this.$store.getters.getShortsCacheByChannel(channel.id)
|
||||
if (cacheEntry == null) { return }
|
||||
|
||||
entries.push(cacheEntry)
|
||||
})
|
||||
return entries
|
||||
},
|
||||
videoCacheForAllActiveProfileChannelsPresent() {
|
||||
if (this.cacheEntriesForAllActiveProfileChannels.length === 0) { return false }
|
||||
if (this.cacheEntriesForAllActiveProfileChannels.length < this.activeSubscriptionList.length) { return false }
|
||||
|
||||
return this.cacheEntriesForAllActiveProfileChannels.every((cacheEntry) => {
|
||||
return cacheEntry.videos != null
|
||||
})
|
||||
},
|
||||
|
||||
activeSubscriptionList: function () {
|
||||
return this.activeProfile.subscriptions
|
||||
},
|
||||
|
||||
fetchSubscriptionsAutomatically: function() {
|
||||
return this.$store.getters.getFetchSubscriptionsAutomatically
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
activeProfile: async function (_) {
|
||||
this.isLoading = true
|
||||
this.loadVideosFromCacheSometimes()
|
||||
},
|
||||
},
|
||||
mounted: async function () {
|
||||
this.isLoading = true
|
||||
|
||||
this.loadVideosFromCacheSometimes()
|
||||
},
|
||||
methods: {
|
||||
loadVideosFromCacheSometimes() {
|
||||
// This method is called on view visible
|
||||
if (this.videoCacheForAllActiveProfileChannelsPresent) {
|
||||
this.loadVideosFromCacheForAllActiveProfileChannels()
|
||||
return
|
||||
}
|
||||
|
||||
this.maybeLoadVideosForSubscriptionsFromRemote()
|
||||
},
|
||||
|
||||
async loadVideosFromCacheForAllActiveProfileChannels() {
|
||||
const videoList = []
|
||||
this.activeSubscriptionList.forEach((channel) => {
|
||||
const channelCacheEntry = this.$store.getters.getShortsCacheByChannel(channel.id)
|
||||
|
||||
videoList.push(...channelCacheEntry.videos)
|
||||
})
|
||||
this.videoList = updateVideoListAfterProcessing(videoList)
|
||||
this.isLoading = false
|
||||
},
|
||||
|
||||
loadVideosForSubscriptionsFromRemote: async function () {
|
||||
if (this.activeSubscriptionList.length === 0) {
|
||||
this.isLoading = false
|
||||
this.videoList = []
|
||||
return
|
||||
}
|
||||
|
||||
const channelsToLoadFromRemote = this.activeSubscriptionList
|
||||
const videoList = []
|
||||
let channelCount = 0
|
||||
this.isLoading = true
|
||||
this.updateShowProgressBar(true)
|
||||
this.setProgressBarPercentage(0)
|
||||
this.attemptedFetch = true
|
||||
|
||||
this.errorChannels = []
|
||||
const videoListFromRemote = (await Promise.all(channelsToLoadFromRemote.map(async (channel) => {
|
||||
let videos = []
|
||||
if (!process.env.IS_ELECTRON || this.backendPreference === 'invidious') {
|
||||
videos = await this.getChannelShortsInvidious(channel)
|
||||
} else {
|
||||
videos = await this.getChannelShortsLocal(channel)
|
||||
}
|
||||
|
||||
channelCount++
|
||||
const percentageComplete = (channelCount / channelsToLoadFromRemote.length) * 100
|
||||
this.setProgressBarPercentage(percentageComplete)
|
||||
this.updateSubscriptionShortsCacheByChannel({
|
||||
channelId: channel.id,
|
||||
videos: videos,
|
||||
})
|
||||
return videos
|
||||
}))).flatMap((o) => o)
|
||||
videoList.push(...videoListFromRemote)
|
||||
|
||||
this.videoList = updateVideoListAfterProcessing(videoList)
|
||||
this.isLoading = false
|
||||
this.updateShowProgressBar(false)
|
||||
},
|
||||
|
||||
maybeLoadVideosForSubscriptionsFromRemote: async function () {
|
||||
if (this.fetchSubscriptionsAutomatically) {
|
||||
// `this.isLoading = false` is called inside `loadVideosForSubscriptionsFromRemote` when needed
|
||||
await this.loadVideosForSubscriptionsFromRemote()
|
||||
} else {
|
||||
this.videoList = []
|
||||
this.attemptedFetch = false
|
||||
this.isLoading = false
|
||||
}
|
||||
},
|
||||
|
||||
getChannelShortsLocal: async function (channel, failedAttempts = 0) {
|
||||
const playlistId = channel.id.replace('UC', 'UUSH')
|
||||
const feedUrl = `https://www.youtube.com/feeds/videos.xml?playlist_id=${playlistId}`
|
||||
|
||||
try {
|
||||
const response = await fetch(feedUrl)
|
||||
|
||||
if (response.status === 404) {
|
||||
return []
|
||||
}
|
||||
|
||||
return await parseYouTubeRSSFeed(await response.text(), channel.id)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
const errorMessage = this.$t('Local API Error (Click to copy)')
|
||||
showToast(`${errorMessage}: ${error}`, 10000, () => {
|
||||
copyToClipboard(error)
|
||||
})
|
||||
switch (failedAttempts) {
|
||||
case 0:
|
||||
if (this.backendFallback) {
|
||||
showToast(this.$t('Falling back to Invidious API'))
|
||||
return this.getChannelShortsInvidious(channel, failedAttempts + 1)
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
default:
|
||||
return []
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getChannelShortsInvidious: async function (channel, failedAttempts = 0) {
|
||||
const playlistId = channel.id.replace('UC', 'UUSH')
|
||||
const feedUrl = `${this.currentInvidiousInstance}/feed/playlist/${playlistId}`
|
||||
|
||||
try {
|
||||
const response = await fetch(feedUrl)
|
||||
|
||||
if (response.status === 500) {
|
||||
return []
|
||||
}
|
||||
|
||||
return await parseYouTubeRSSFeed(await response.text(), channel.id)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
const errorMessage = this.$t('Invidious API Error (Click to copy)')
|
||||
showToast(`${errorMessage}: ${error}`, 10000, () => {
|
||||
copyToClipboard(error)
|
||||
})
|
||||
switch (failedAttempts) {
|
||||
case 0:
|
||||
if (process.env.IS_ELECTRON && this.backendFallback) {
|
||||
showToast(this.$t('Falling back to the local API'))
|
||||
return this.getChannelShortsLocal(channel, failedAttempts + 1)
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
default:
|
||||
return []
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
...mapActions([
|
||||
'updateShowProgressBar',
|
||||
'updateSubscriptionShortsCacheByChannel',
|
||||
]),
|
||||
|
||||
...mapMutations([
|
||||
'setProgressBarPercentage'
|
||||
])
|
||||
}
|
||||
})
|
|
@ -0,0 +1,11 @@
|
|||
<template>
|
||||
<subscriptions-tab-ui
|
||||
:is-loading="isLoading"
|
||||
:video-list="videoList"
|
||||
:error-channels="errorChannels"
|
||||
:attempted-fetch="attemptedFetch"
|
||||
@refresh="loadVideosForSubscriptionsFromRemote"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script src="./subscriptions-shorts.js" />
|
|
@ -0,0 +1,31 @@
|
|||
.card {
|
||||
width: 85%;
|
||||
margin: 0 auto;
|
||||
margin-bottom: 60px;
|
||||
}
|
||||
|
||||
.message {
|
||||
color: var(--tertiary-text-color);
|
||||
}
|
||||
|
||||
.floatingTopButton {
|
||||
position: fixed;
|
||||
top: 70px;
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
.channelBubble {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 350px) {
|
||||
.floatingTopButton {
|
||||
position: absolute
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 680px) {
|
||||
.card {
|
||||
width: 90%;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
import { defineComponent } from 'vue'
|
||||
|
||||
import FtLoader from '../ft-loader/ft-loader.vue'
|
||||
import FtCard from '../ft-card/ft-card.vue'
|
||||
import FtButton from '../ft-button/ft-button.vue'
|
||||
import FtIconButton from '../ft-icon-button/ft-icon-button.vue'
|
||||
import FtFlexBox from '../ft-flex-box/ft-flex-box.vue'
|
||||
import FtElementList from '../ft-element-list/ft-element-list.vue'
|
||||
import FtChannelBubble from '../ft-channel-bubble/ft-channel-bubble.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SubscriptionsTabUI',
|
||||
components: {
|
||||
'ft-loader': FtLoader,
|
||||
'ft-card': FtCard,
|
||||
'ft-button': FtButton,
|
||||
'ft-icon-button': FtIconButton,
|
||||
'ft-flex-box': FtFlexBox,
|
||||
'ft-element-list': FtElementList,
|
||||
'ft-channel-bubble': FtChannelBubble
|
||||
},
|
||||
props: {
|
||||
isLoading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
videoList: {
|
||||
type: Array,
|
||||
default: () => ([])
|
||||
},
|
||||
errorChannels: {
|
||||
type: Array,
|
||||
default: () => ([])
|
||||
},
|
||||
attemptedFetch: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
dataLimit: 100,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
activeVideoList: function () {
|
||||
if (this.videoList.length < this.dataLimit) {
|
||||
return this.videoList
|
||||
} else {
|
||||
return this.videoList.slice(0, this.dataLimit)
|
||||
}
|
||||
},
|
||||
|
||||
activeProfile: function () {
|
||||
return this.$store.getters.getActiveProfile
|
||||
},
|
||||
|
||||
activeSubscriptionList: function () {
|
||||
return this.activeProfile.subscriptions
|
||||
},
|
||||
|
||||
fetchSubscriptionsAutomatically: function() {
|
||||
return this.$store.getters.getFetchSubscriptionsAutomatically
|
||||
},
|
||||
},
|
||||
created: function () {
|
||||
const dataLimit = sessionStorage.getItem('subscriptionLimit')
|
||||
|
||||
if (dataLimit !== null) {
|
||||
this.dataLimit = dataLimit
|
||||
}
|
||||
},
|
||||
mounted: async function () {
|
||||
document.addEventListener('keydown', this.keyboardShortcutHandler)
|
||||
},
|
||||
beforeDestroy: function () {
|
||||
document.removeEventListener('keydown', this.keyboardShortcutHandler)
|
||||
},
|
||||
methods: {
|
||||
goToChannel: function (id) {
|
||||
this.$router.push({ path: `/channel/${id}` })
|
||||
},
|
||||
|
||||
increaseLimit: function () {
|
||||
this.dataLimit += 100
|
||||
sessionStorage.setItem('subscriptionLimit', this.dataLimit)
|
||||
},
|
||||
|
||||
/**
|
||||
* This function `keyboardShortcutHandler` should always be at the bottom of this file
|
||||
* @param {KeyboardEvent} event the keyboard event
|
||||
*/
|
||||
keyboardShortcutHandler: function (event) {
|
||||
if (event.ctrlKey || document.activeElement.classList.contains('ft-input')) {
|
||||
return
|
||||
}
|
||||
// Avoid handling events due to user holding a key (not released)
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/repeat
|
||||
if (event.repeat) { return }
|
||||
|
||||
switch (event.key) {
|
||||
case 'r':
|
||||
case 'R':
|
||||
if (!this.isLoading) {
|
||||
this.$emit('refresh')
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
|
@ -0,0 +1,72 @@
|
|||
<template>
|
||||
<div>
|
||||
<ft-loader
|
||||
v-if="isLoading"
|
||||
/>
|
||||
<div
|
||||
v-if="!isLoading && errorChannels.length !== 0"
|
||||
>
|
||||
<h3> {{ $t("Subscriptions.Error Channels") }}</h3>
|
||||
<div>
|
||||
<ft-channel-bubble
|
||||
v-for="(channel, index) in errorChannels"
|
||||
:key="index"
|
||||
:channel-name="channel.name"
|
||||
:channel-id="channel.id"
|
||||
:channel-thumbnail="channel.thumbnail"
|
||||
class="channelBubble"
|
||||
@click="goToChannel(channel.id)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<ft-flex-box
|
||||
v-if="!isLoading && activeVideoList.length === 0"
|
||||
>
|
||||
<p
|
||||
v-if="activeSubscriptionList.length === 0"
|
||||
class="message"
|
||||
>
|
||||
{{ $t("Subscriptions['Your Subscription list is currently empty. Start adding subscriptions to see them here.']") }}
|
||||
</p>
|
||||
<p
|
||||
v-else-if="!fetchSubscriptionsAutomatically && !attemptedFetch"
|
||||
class="message"
|
||||
>
|
||||
{{ $t("Subscriptions.Disabled Automatic Fetching") }}
|
||||
</p>
|
||||
<p
|
||||
v-else
|
||||
class="message"
|
||||
>
|
||||
{{ $t("Subscriptions.Empty Channels") }}
|
||||
</p>
|
||||
</ft-flex-box>
|
||||
<ft-element-list
|
||||
v-if="!isLoading && activeVideoList.length > 0"
|
||||
:data="activeVideoList"
|
||||
:use-channels-hidden-preference="false"
|
||||
/>
|
||||
<ft-flex-box
|
||||
v-if="!isLoading && videoList.length > dataLimit"
|
||||
>
|
||||
<ft-button
|
||||
:label="$t('Subscriptions.Load More Videos')"
|
||||
background-color="var(--primary-color)"
|
||||
text-color="var(--text-with-main-color)"
|
||||
@click="increaseLimit"
|
||||
/>
|
||||
</ft-flex-box>
|
||||
<ft-icon-button
|
||||
v-if="!isLoading"
|
||||
:icon="['fas', 'sync']"
|
||||
class="floatingTopButton"
|
||||
:title="$t('Subscriptions.Refresh Subscriptions')"
|
||||
:size="12"
|
||||
theme="primary"
|
||||
@click="$emit('refresh')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./subscriptions-tab-ui.js" />
|
||||
<style scoped src="./subscriptions-tab-ui.css" />
|
|
@ -0,0 +1,334 @@
|
|||
import { defineComponent } from 'vue'
|
||||
import { mapActions, mapMutations } from 'vuex'
|
||||
import SubscriptionsTabUI from '../subscriptions-tab-ui/subscriptions-tab-ui.vue'
|
||||
|
||||
import { copyToClipboard, showToast } from '../../helpers/utils'
|
||||
import { invidiousAPICall } from '../../helpers/api/invidious'
|
||||
import { getLocalChannelVideos } from '../../helpers/api/local'
|
||||
import { addPublishedDatesInvidious, addPublishedDatesLocal, parseYouTubeRSSFeed, updateVideoListAfterProcessing } from '../../helpers/subscriptions'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SubscriptionsVideos',
|
||||
components: {
|
||||
'subscriptions-tab-ui': SubscriptionsTabUI
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
isLoading: false,
|
||||
videoList: [],
|
||||
errorChannels: [],
|
||||
attemptedFetch: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
backendPreference: function () {
|
||||
return this.$store.getters.getBackendPreference
|
||||
},
|
||||
|
||||
backendFallback: function () {
|
||||
return this.$store.getters.getBackendFallback
|
||||
},
|
||||
|
||||
currentInvidiousInstance: function () {
|
||||
return this.$store.getters.getCurrentInvidiousInstance
|
||||
},
|
||||
|
||||
useRssFeeds: function () {
|
||||
return this.$store.getters.getUseRssFeeds
|
||||
},
|
||||
|
||||
activeProfile: function () {
|
||||
return this.$store.getters.getActiveProfile
|
||||
},
|
||||
activeProfileId: function () {
|
||||
return this.activeProfile._id
|
||||
},
|
||||
|
||||
cacheEntriesForAllActiveProfileChannels() {
|
||||
const entries = []
|
||||
this.activeSubscriptionList.forEach((channel) => {
|
||||
const cacheEntry = this.$store.getters.getVideoCacheByChannel(channel.id)
|
||||
if (cacheEntry == null) { return }
|
||||
|
||||
entries.push(cacheEntry)
|
||||
})
|
||||
return entries
|
||||
},
|
||||
videoCacheForAllActiveProfileChannelsPresent() {
|
||||
if (this.cacheEntriesForAllActiveProfileChannels.length === 0) { return false }
|
||||
if (this.cacheEntriesForAllActiveProfileChannels.length < this.activeSubscriptionList.length) { return false }
|
||||
|
||||
return this.cacheEntriesForAllActiveProfileChannels.every((cacheEntry) => {
|
||||
return cacheEntry.videos != null
|
||||
})
|
||||
},
|
||||
|
||||
activeSubscriptionList: function () {
|
||||
return this.activeProfile.subscriptions
|
||||
},
|
||||
|
||||
fetchSubscriptionsAutomatically: function() {
|
||||
return this.$store.getters.getFetchSubscriptionsAutomatically
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
activeProfile: async function (_) {
|
||||
this.isLoading = true
|
||||
this.loadVideosFromCacheSometimes()
|
||||
},
|
||||
},
|
||||
mounted: async function () {
|
||||
this.isLoading = true
|
||||
|
||||
this.loadVideosFromCacheSometimes()
|
||||
},
|
||||
methods: {
|
||||
loadVideosFromCacheSometimes() {
|
||||
// This method is called on view visible
|
||||
if (this.videoCacheForAllActiveProfileChannelsPresent) {
|
||||
this.loadVideosFromCacheForAllActiveProfileChannels()
|
||||
return
|
||||
}
|
||||
|
||||
this.maybeLoadVideosForSubscriptionsFromRemote()
|
||||
},
|
||||
|
||||
async loadVideosFromCacheForAllActiveProfileChannels() {
|
||||
const videoList = []
|
||||
this.activeSubscriptionList.forEach((channel) => {
|
||||
const channelCacheEntry = this.$store.getters.getVideoCacheByChannel(channel.id)
|
||||
|
||||
videoList.push(...channelCacheEntry.videos)
|
||||
})
|
||||
this.videoList = updateVideoListAfterProcessing(videoList)
|
||||
this.isLoading = false
|
||||
},
|
||||
|
||||
loadVideosForSubscriptionsFromRemote: async function () {
|
||||
if (this.activeSubscriptionList.length === 0) {
|
||||
this.isLoading = false
|
||||
this.videoList = []
|
||||
return
|
||||
}
|
||||
|
||||
const channelsToLoadFromRemote = this.activeSubscriptionList
|
||||
const videoList = []
|
||||
let channelCount = 0
|
||||
this.isLoading = true
|
||||
|
||||
let useRss = this.useRssFeeds
|
||||
if (channelsToLoadFromRemote.length >= 125 && !useRss) {
|
||||
showToast(
|
||||
this.$t('Subscriptions["This profile has a large number of subscriptions. Forcing RSS to avoid rate limiting"]'),
|
||||
10000
|
||||
)
|
||||
useRss = true
|
||||
}
|
||||
this.updateShowProgressBar(true)
|
||||
this.setProgressBarPercentage(0)
|
||||
this.attemptedFetch = true
|
||||
|
||||
this.errorChannels = []
|
||||
const videoListFromRemote = (await Promise.all(channelsToLoadFromRemote.map(async (channel) => {
|
||||
let videos = []
|
||||
if (!process.env.IS_ELECTRON || this.backendPreference === 'invidious') {
|
||||
if (useRss) {
|
||||
videos = await this.getChannelVideosInvidiousRSS(channel)
|
||||
} else {
|
||||
videos = await this.getChannelVideosInvidiousScraper(channel)
|
||||
}
|
||||
} else {
|
||||
if (useRss) {
|
||||
videos = await this.getChannelVideosLocalRSS(channel)
|
||||
} else {
|
||||
videos = await this.getChannelVideosLocalScraper(channel)
|
||||
}
|
||||
}
|
||||
|
||||
channelCount++
|
||||
const percentageComplete = (channelCount / channelsToLoadFromRemote.length) * 100
|
||||
this.setProgressBarPercentage(percentageComplete)
|
||||
this.updateSubscriptionVideosCacheByChannel({
|
||||
channelId: channel.id,
|
||||
videos: videos,
|
||||
})
|
||||
return videos
|
||||
}))).flatMap((o) => o)
|
||||
videoList.push(...videoListFromRemote)
|
||||
|
||||
this.videoList = updateVideoListAfterProcessing(videoList)
|
||||
this.isLoading = false
|
||||
this.updateShowProgressBar(false)
|
||||
},
|
||||
|
||||
maybeLoadVideosForSubscriptionsFromRemote: async function () {
|
||||
if (this.fetchSubscriptionsAutomatically) {
|
||||
// `this.isLoading = false` is called inside `loadVideosForSubscriptionsFromRemote` when needed
|
||||
await this.loadVideosForSubscriptionsFromRemote()
|
||||
} else {
|
||||
this.videoList = []
|
||||
this.attemptedFetch = false
|
||||
this.isLoading = false
|
||||
}
|
||||
},
|
||||
|
||||
getChannelVideosLocalScraper: async function (channel, failedAttempts = 0) {
|
||||
try {
|
||||
const videos = await getLocalChannelVideos(channel.id)
|
||||
|
||||
if (videos === null) {
|
||||
this.errorChannels.push(channel)
|
||||
return []
|
||||
}
|
||||
|
||||
addPublishedDatesLocal(videos)
|
||||
|
||||
return videos
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
const errorMessage = this.$t('Local API Error (Click to copy)')
|
||||
showToast(`${errorMessage}: ${err}`, 10000, () => {
|
||||
copyToClipboard(err)
|
||||
})
|
||||
switch (failedAttempts) {
|
||||
case 0:
|
||||
return await this.getChannelVideosLocalRSS(channel, failedAttempts + 1)
|
||||
case 1:
|
||||
if (this.backendFallback) {
|
||||
showToast(this.$t('Falling back to Invidious API'))
|
||||
return await this.getChannelVideosInvidiousScraper(channel, failedAttempts + 1)
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
case 2:
|
||||
return await this.getChannelVideosLocalRSS(channel, failedAttempts + 1)
|
||||
default:
|
||||
return []
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getChannelVideosLocalRSS: async function (channel, failedAttempts = 0) {
|
||||
const playlistId = channel.id.replace('UC', 'UULF')
|
||||
const feedUrl = `https://www.youtube.com/feeds/videos.xml?playlist_id=${playlistId}`
|
||||
|
||||
try {
|
||||
const response = await fetch(feedUrl)
|
||||
|
||||
if (response.status === 404) {
|
||||
this.errorChannels.push(channel)
|
||||
return []
|
||||
}
|
||||
|
||||
return await parseYouTubeRSSFeed(await response.text(), channel.id)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
const errorMessage = this.$t('Local API Error (Click to copy)')
|
||||
showToast(`${errorMessage}: ${error}`, 10000, () => {
|
||||
copyToClipboard(error)
|
||||
})
|
||||
switch (failedAttempts) {
|
||||
case 0:
|
||||
return this.getChannelVideosLocalScraper(channel, failedAttempts + 1)
|
||||
case 1:
|
||||
if (this.backendFallback) {
|
||||
showToast(this.$t('Falling back to Invidious API'))
|
||||
return this.getChannelVideosInvidiousRSS(channel, failedAttempts + 1)
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
case 2:
|
||||
return this.getChannelVideosLocalScraper(channel, failedAttempts + 1)
|
||||
default:
|
||||
return []
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getChannelVideosInvidiousScraper: function (channel, failedAttempts = 0) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const subscriptionsPayload = {
|
||||
resource: 'channels/latest',
|
||||
id: channel.id,
|
||||
params: {}
|
||||
}
|
||||
|
||||
invidiousAPICall(subscriptionsPayload).then((result) => {
|
||||
addPublishedDatesInvidious(result.videos)
|
||||
|
||||
resolve(result.videos)
|
||||
}).catch((err) => {
|
||||
console.error(err)
|
||||
const errorMessage = this.$t('Invidious API Error (Click to copy)')
|
||||
showToast(`${errorMessage}: ${err.responseText}`, 10000, () => {
|
||||
copyToClipboard(err.responseText)
|
||||
})
|
||||
switch (failedAttempts) {
|
||||
case 0:
|
||||
resolve(this.getChannelVideosInvidiousRSS(channel, failedAttempts + 1))
|
||||
break
|
||||
case 1:
|
||||
if (process.env.IS_ELECTRON && this.backendFallback) {
|
||||
showToast(this.$t('Falling back to the local API'))
|
||||
resolve(this.getChannelVideosLocalScraper(channel, failedAttempts + 1))
|
||||
} else {
|
||||
resolve([])
|
||||
}
|
||||
break
|
||||
case 2:
|
||||
resolve(this.getChannelVideosInvidiousRSS(channel, failedAttempts + 1))
|
||||
break
|
||||
default:
|
||||
resolve([])
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
getChannelVideosInvidiousRSS: async function (channel, failedAttempts = 0) {
|
||||
const playlistId = channel.id.replace('UC', 'UULF')
|
||||
const feedUrl = `${this.currentInvidiousInstance}/feed/playlist/${playlistId}`
|
||||
|
||||
try {
|
||||
const response = await fetch(feedUrl)
|
||||
|
||||
if (response.status === 500) {
|
||||
this.errorChannels.push(channel)
|
||||
return []
|
||||
}
|
||||
|
||||
return await parseYouTubeRSSFeed(await response.text(), channel.id)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
const errorMessage = this.$t('Invidious API Error (Click to copy)')
|
||||
showToast(`${errorMessage}: ${error}`, 10000, () => {
|
||||
copyToClipboard(error)
|
||||
})
|
||||
switch (failedAttempts) {
|
||||
case 0:
|
||||
return this.getChannelVideosInvidiousScraper(channel, failedAttempts + 1)
|
||||
case 1:
|
||||
if (process.env.IS_ELECTRON && this.backendFallback) {
|
||||
showToast(this.$t('Falling back to the local API'))
|
||||
return this.getChannelVideosLocalRSS(channel, failedAttempts + 1)
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
case 2:
|
||||
return this.getChannelVideosInvidiousScraper(channel, failedAttempts + 1)
|
||||
default:
|
||||
return []
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
...mapActions([
|
||||
'updateShowProgressBar',
|
||||
'updateSubscriptionVideosCacheByChannel',
|
||||
]),
|
||||
|
||||
...mapMutations([
|
||||
'setProgressBarPercentage'
|
||||
])
|
||||
}
|
||||
})
|
|
@ -0,0 +1,11 @@
|
|||
<template>
|
||||
<subscriptions-tab-ui
|
||||
:is-loading="isLoading"
|
||||
:video-list="videoList"
|
||||
:error-channels="errorChannels"
|
||||
:attempted-fetch="attemptedFetch"
|
||||
@refresh="loadVideosForSubscriptionsFromRemote"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script src="./subscriptions-videos.js" />
|
|
@ -22,6 +22,7 @@
|
|||
:data="video"
|
||||
appearance="recommendation"
|
||||
force-list-type="list"
|
||||
:use-channels-hidden-preference="true"
|
||||
/>
|
||||
</ft-card>
|
||||
</template>
|
||||
|
|
|
@ -152,27 +152,6 @@ export async function getLocalVideoInfo(id, attemptBypass = false) {
|
|||
player = innertube.actions.session.player
|
||||
|
||||
info = await innertube.getInfo(id)
|
||||
|
||||
// // the android streaming formats don't seem to be throttled at the moment so we use those if they are availabe
|
||||
try {
|
||||
const androidInnertube = await createInnertube({ clientType: ClientType.ANDROID, generateSessionLocally: false })
|
||||
const androidInfo = await androidInnertube.getBasicInfo(id, 'ANDROID')
|
||||
|
||||
// Sometimes when YouTube detects a third party client or has applied an IP-ratelimit,
|
||||
// they replace the response with a different video id
|
||||
// https://github.com/TeamNewPipe/NewPipe/issues/8713
|
||||
// https://github.com/TeamPiped/Piped/issues/2487
|
||||
if (androidInfo.basic_info.id !== id) {
|
||||
console.error(`Failed to fetch android formats. Wrong video ID in response: ${androidInfo.basic_info.id}, expected: ${id}`)
|
||||
} else if (androidInfo.playability_status.status !== 'OK') {
|
||||
console.error('Failed to fetch android formats', JSON.stringify(androidInfo.playability_status))
|
||||
} else {
|
||||
info.streaming_data = androidInfo.streaming_data
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch android formats')
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
if (info.streaming_data) {
|
||||
|
@ -188,17 +167,24 @@ export async function getLocalComments(id, sortByNewest = false) {
|
|||
return innertube.getComments(id, sortByNewest ? 'NEWEST_FIRST' : 'TOP_COMMENTS')
|
||||
}
|
||||
|
||||
// I know `type & type` is typescript syntax and not valid jsdoc but I couldn't get @extends or @augments to work
|
||||
|
||||
/**
|
||||
* @typedef {object} _LocalFormat
|
||||
* @property {string} freeTubeUrl deciphered streaming URL, stored in a custom property so the DASH manifest generation doesn't break
|
||||
*
|
||||
* @typedef {Misc.Format & _LocalFormat} LocalFormat
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {Misc.Format[]} formats
|
||||
* @param {import('youtubei.js').Player} player
|
||||
*/
|
||||
function decipherFormats(formats, player) {
|
||||
for (const format of formats) {
|
||||
format.url = format.decipher(player)
|
||||
|
||||
// set these to undefined so that toDash doesn't try to decipher them again, throwing an error
|
||||
format.cipher = undefined
|
||||
format.signature_cipher = undefined
|
||||
// toDash deciphers the format again, so if we overwrite the original URL,
|
||||
// it breaks because the n param would get deciphered twice and then be incorrect
|
||||
format.freeTubeUrl = format.decipher(player)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -270,6 +256,36 @@ export async function getLocalChannelVideos(id) {
|
|||
}
|
||||
}
|
||||
|
||||
export async function getLocalChannelLiveStreams(id) {
|
||||
const innertube = await createInnertube()
|
||||
|
||||
try {
|
||||
const response = await innertube.actions.execute(Endpoints.BrowseEndpoint.PATH, Endpoints.BrowseEndpoint.build({
|
||||
browse_id: id,
|
||||
params: 'EgdzdHJlYW1z8gYECgJ6AA%3D%3D'
|
||||
// protobuf for the live tab (this is the one that YouTube uses,
|
||||
// it has some empty fields in the protobuf but it doesn't work if you remove them)
|
||||
}))
|
||||
|
||||
const liveStreamsTab = new YT.Channel(null, response)
|
||||
|
||||
// if the channel doesn't have a live tab, YouTube returns the home tab instead
|
||||
// so we need to check that we got the right tab
|
||||
if (liveStreamsTab.current_tab?.endpoint.metadata.url?.endsWith('/streams')) {
|
||||
return parseLocalChannelVideos(liveStreamsTab.videos, liveStreamsTab.header.author)
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
if (error instanceof Utils.ChannelError) {
|
||||
return null
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('youtubei.js').YTNodes.Video[]} videos
|
||||
* @param {Misc.Author} author
|
||||
|
@ -377,8 +393,7 @@ export function parseLocalListPlaylist(playlist, author = undefined) {
|
|||
let channelId = null
|
||||
/** @type {import('youtubei.js').YTNodes.PlaylistVideoThumbnail} */
|
||||
const thumbnailRenderer = playlist.thumbnail_renderer
|
||||
|
||||
if (playlist.author) {
|
||||
if (playlist.author && playlist.author.id !== 'N/A') {
|
||||
if (playlist.author instanceof Misc.Text) {
|
||||
channelName = playlist.author.text
|
||||
|
||||
|
@ -425,7 +440,8 @@ function handleSearchResponse(response) {
|
|||
|
||||
return {
|
||||
results,
|
||||
continuationData: response.has_continuation ? response : null
|
||||
// check the length of the results, as there can be continuations for things that we've filtered out, which we don't want
|
||||
continuationData: response.has_continuation && results.length > 0 ? response : null
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -451,11 +467,47 @@ export function parseLocalPlaylistVideo(video) {
|
|||
/** @type {import('youtubei.js').YTNodes.PlaylistVideo} */
|
||||
const video_ = video
|
||||
|
||||
let viewCount = null
|
||||
|
||||
// the accessiblity label contains the full view count
|
||||
// the video info only contains the short view count
|
||||
if (video_.accessibility_label) {
|
||||
const match = video_.accessibility_label.match(/([\d,.]+|no) views?$/i)
|
||||
|
||||
if (match) {
|
||||
const count = match[1]
|
||||
|
||||
// as it's rare that a video has no views,
|
||||
// checking the length allows us to avoid running toLowerCase unless we have to
|
||||
if (count.length === 2 && count.toLowerCase() === 'no') {
|
||||
viewCount = 0
|
||||
} else {
|
||||
const views = extractNumberFromString(count)
|
||||
|
||||
if (!isNaN(views)) {
|
||||
viewCount = views
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let publishedText = null
|
||||
|
||||
// normal videos have 3 text runs with the last one containing the published date
|
||||
// live videos have 2 text runs with the number of people watching
|
||||
// upcoming either videos don't have any info text or the number of people waiting,
|
||||
// but we have the premiere date for those, so we don't need the published date
|
||||
if (video_.video_info.runs && video_.video_info.runs.length === 3) {
|
||||
publishedText = video_.video_info.runs[2].text
|
||||
}
|
||||
|
||||
return {
|
||||
videoId: video_.id,
|
||||
title: video_.title.text,
|
||||
author: video_.author.name,
|
||||
authorId: video_.author.id,
|
||||
viewCount,
|
||||
publishedText,
|
||||
lengthSeconds: isNaN(video_.duration.seconds) ? '' : video_.duration.seconds,
|
||||
liveNow: video_.is_live,
|
||||
isUpcoming: video_.is_upcoming,
|
||||
|
@ -705,7 +757,7 @@ export function parseLocalTextRuns(runs, emojiSize = 16, options = { looseChanne
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {Misc.Format} format
|
||||
* @param {LocalFormat} format
|
||||
*/
|
||||
export function mapLocalFormat(format) {
|
||||
return {
|
||||
|
@ -716,7 +768,7 @@ export function mapLocalFormat(format) {
|
|||
mimeType: format.mime_type,
|
||||
height: format.height,
|
||||
width: format.width,
|
||||
url: format.url
|
||||
url: format.freeTubeUrl
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
import store from '../store/index'
|
||||
import { calculatePublishedDate } from './utils'
|
||||
|
||||
/**
|
||||
* Filtering and sort based on user preferences
|
||||
* @param {any[]} videos
|
||||
*/
|
||||
export function updateVideoListAfterProcessing(videos) {
|
||||
let videoList = videos
|
||||
|
||||
// Filtering and sorting based in preference
|
||||
videoList.sort((a, b) => {
|
||||
return b.publishedDate - a.publishedDate
|
||||
})
|
||||
|
||||
if (store.getters.getHideLiveStreams) {
|
||||
videoList = videoList.filter(item => {
|
||||
return (!item.liveNow && !item.isUpcoming)
|
||||
})
|
||||
}
|
||||
|
||||
if (store.getters.getHideUpcomingPremieres) {
|
||||
videoList = videoList.filter(item => {
|
||||
if (item.isRSS) {
|
||||
// viewCount is our only method of detecting premieres in RSS
|
||||
// data without sending an additional request.
|
||||
// If we ever get a better flag, use it here instead.
|
||||
return item.viewCount !== '0'
|
||||
}
|
||||
// Observed for premieres in Local API Subscriptions.
|
||||
return (item.premiereDate == null ||
|
||||
// Invidious API
|
||||
// `premiereTimestamp` only available on premiered videos
|
||||
// https://docs.invidious.io/api/common_types/#videoobject
|
||||
item.premiereTimestamp == null
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
if (store.getters.getHideWatchedSubs) {
|
||||
const historyCache = store.getters.getHistoryCache
|
||||
|
||||
videoList = videoList.filter((video) => {
|
||||
const historyIndex = historyCache.findIndex((x) => {
|
||||
return x.videoId === video.videoId
|
||||
})
|
||||
|
||||
return historyIndex === -1
|
||||
})
|
||||
}
|
||||
|
||||
return videoList
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} rssString
|
||||
* @param {string} channelId
|
||||
*/
|
||||
export async function parseYouTubeRSSFeed(rssString, channelId) {
|
||||
// doesn't need to be asynchronous, but doing it allows us to do the relatively slow DOM querying in parallel
|
||||
try {
|
||||
const xmlDom = new DOMParser().parseFromString(rssString, 'application/xml')
|
||||
const channelName = xmlDom.querySelector('author > name').textContent
|
||||
const entries = xmlDom.querySelectorAll('entry')
|
||||
|
||||
const promises = []
|
||||
|
||||
for (const entry of entries) {
|
||||
promises.push(parseRSSEntry(entry, channelId, channelName))
|
||||
}
|
||||
|
||||
return await Promise.all(promises)
|
||||
} catch (e) {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Element} entry
|
||||
* @param {string} channelId
|
||||
* @param {string} channelName
|
||||
*/
|
||||
async function parseRSSEntry(entry, channelId, channelName) {
|
||||
// doesn't need to be asynchronous, but doing it allows us to do the relatively slow DOM querying in parallel
|
||||
const published = new Date(entry.querySelector('published').textContent)
|
||||
|
||||
return {
|
||||
authorId: channelId,
|
||||
author: channelName,
|
||||
// querySelector doesn't support xml namespaces so we have to use getElementsByTagName here
|
||||
videoId: entry.getElementsByTagName('yt:videoId')[0].textContent,
|
||||
title: entry.querySelector('title').textContent,
|
||||
publishedDate: published,
|
||||
publishedText: published.toLocaleString(),
|
||||
viewCount: entry.getElementsByTagName('media:statistics')[0]?.getAttribute('views') || null,
|
||||
type: 'video',
|
||||
lengthSeconds: '0:00',
|
||||
isRSS: true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* liveNow: boolean,
|
||||
* isUpcoming: boolean,
|
||||
* premiereDate: Date,
|
||||
* publishedText: string,
|
||||
* publishedDate: number
|
||||
* }[]} videos publishedDate is added by this function,
|
||||
* but adding it to the type definition stops vscode warning that the property doesn't exist
|
||||
*/
|
||||
export function addPublishedDatesLocal(videos) {
|
||||
videos.forEach(video => {
|
||||
if (video.liveNow) {
|
||||
video.publishedDate = new Date().getTime()
|
||||
} else if (video.isUpcoming) {
|
||||
video.publishedDate = video.premiereDate
|
||||
} else {
|
||||
video.publishedDate = calculatePublishedDate(video.publishedText)
|
||||
}
|
||||
return video
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* liveNow: boolean,
|
||||
* isUpcoming: boolean,
|
||||
* premiereTimestamp: number,
|
||||
* published: number,
|
||||
* publishedDate: number
|
||||
* }[]} videos publishedDate is added by this function,
|
||||
* but adding it to the type definition stops vscode warning that the property doesn't exist
|
||||
*/
|
||||
export function addPublishedDatesInvidious(videos) {
|
||||
videos.forEach(video => {
|
||||
if (video.liveNow) {
|
||||
video.publishedDate = new Date().getTime()
|
||||
} else if (video.isUpcoming) {
|
||||
video.publishedDate = new Date(video.premiereTimestamp * 1000)
|
||||
} else {
|
||||
video.publishedDate = new Date(video.published * 1000)
|
||||
}
|
||||
return video
|
||||
})
|
||||
}
|
|
@ -197,10 +197,10 @@ export function showToast(message, time = null, action = null) {
|
|||
* a toast with the error is shown. If the copy is successful and
|
||||
* there is a success message, a toast with that message is shown.
|
||||
* @param {string} content the content to be copied to the clipboard
|
||||
* @param {string} messageOnSuccess the message to be displayed as a toast when the copy succeeds (optional)
|
||||
* @param {string} messageOnError the message to be displayed as a toast when the copy fails (optional)
|
||||
* @param {null|string} messageOnSuccess the message to be displayed as a toast when the copy succeeds (optional)
|
||||
* @param {null|string} messageOnError the message to be displayed as a toast when the copy fails (optional)
|
||||
*/
|
||||
export async function copyToClipboard(content, { messageOnSuccess = null, messageOnError = null }) {
|
||||
export async function copyToClipboard(content, { messageOnSuccess = null, messageOnError = null } = {}) {
|
||||
if (navigator.clipboard !== undefined && window.isSecureContext) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(content)
|
||||
|
|
|
@ -228,6 +228,7 @@ $watched-transition-duration: 0.5s;
|
|||
font-size: 14px;
|
||||
grid-area: infoLine;
|
||||
margin-top: 5px;
|
||||
overflow-wrap: anywhere;
|
||||
|
||||
@include is-sidebar-item {
|
||||
font-size: 12px;
|
||||
|
|
|
@ -195,6 +195,8 @@ const state = {
|
|||
hideActiveSubscriptions: false,
|
||||
hideChannelCommunity: false,
|
||||
hideChannelPlaylists: false,
|
||||
hideChannelReleases: false,
|
||||
hideChannelPodcasts: false,
|
||||
hideChannelShorts: false,
|
||||
hideChannelSubscriptions: false,
|
||||
hideCommentLikes: false,
|
||||
|
@ -210,6 +212,9 @@ const state = {
|
|||
hideRecommendedVideos: false,
|
||||
hideSearchBar: false,
|
||||
hideSharingActions: false,
|
||||
hideSubscriptionsVideos: false,
|
||||
hideSubscriptionsShorts: false,
|
||||
hideSubscriptionsLive: false,
|
||||
hideTrendingVideos: false,
|
||||
hideUnsubscribeButton: false,
|
||||
hideUpcomingPremieres: false,
|
||||
|
|
|
@ -7,35 +7,91 @@ function deepCopy(obj) {
|
|||
}
|
||||
|
||||
const state = {
|
||||
subscriptionsCachePerChannel: {},
|
||||
videoCache: {},
|
||||
liveCache: {},
|
||||
shortsCache: {}
|
||||
}
|
||||
|
||||
const getters = {
|
||||
getSubscriptionsCacheEntriesForOneChannel: (state) => (channelId) => {
|
||||
return state.subscriptionsCachePerChannel[channelId]
|
||||
getVideoCache: (state) => {
|
||||
return state.videoCache
|
||||
},
|
||||
|
||||
getVideoCacheByChannel: (state) => (channelId) => {
|
||||
return state.videoCache[channelId]
|
||||
},
|
||||
|
||||
getShortsCache: (state) => {
|
||||
return state.shortsCache
|
||||
},
|
||||
|
||||
getShortsCacheByChannel: (state) => (channelId) => {
|
||||
return state.shortsCache[channelId]
|
||||
},
|
||||
|
||||
getLiveCache: (state) => {
|
||||
return state.liveCache
|
||||
},
|
||||
|
||||
getLiveCacheByChannel: (state) => (channelId) => {
|
||||
return state.liveCache[channelId]
|
||||
}
|
||||
}
|
||||
|
||||
const actions = {
|
||||
clearSubscriptionsCache: ({ commit }) => {
|
||||
commit('clearSubscriptionsCachePerChannel')
|
||||
clearSubscriptionVideosCache: ({ commit }) => {
|
||||
commit('clearVideoCache')
|
||||
},
|
||||
|
||||
updateSubscriptionsCacheForOneChannel: ({ commit }, payload) => {
|
||||
commit('updateSubscriptionsCacheForOneChannel', payload)
|
||||
updateSubscriptionVideosCacheByChannel: ({ commit }, payload) => {
|
||||
commit('updateVideoCacheByChannel', payload)
|
||||
},
|
||||
|
||||
clearSubscriptionShortsCache: ({ commit }) => {
|
||||
commit('clearShortsCache')
|
||||
},
|
||||
|
||||
updateSubscriptionShortsCacheByChannel: ({ commit }, payload) => {
|
||||
commit('updateShortsCacheByChannel', payload)
|
||||
},
|
||||
|
||||
clearSubscriptionLiveCache: ({ commit }) => {
|
||||
commit('clearLiveCache')
|
||||
},
|
||||
|
||||
updateSubscriptionLiveCacheByChannel: ({ commit }, payload) => {
|
||||
commit('updateLiveCacheByChannel', payload)
|
||||
}
|
||||
}
|
||||
|
||||
const mutations = {
|
||||
updateSubscriptionsCacheForOneChannel(state, { channelId, videos }) {
|
||||
const existingObject = state.subscriptionsCachePerChannel[channelId]
|
||||
updateVideoCacheByChannel(state, { channelId, videos }) {
|
||||
const existingObject = state.videoCache[channelId]
|
||||
const newObject = existingObject != null ? existingObject : deepCopy(defaultCacheEntryValueForForOneChannel)
|
||||
if (videos != null) { newObject.videos = videos }
|
||||
state.subscriptionsCachePerChannel[channelId] = newObject
|
||||
state.videoCache[channelId] = newObject
|
||||
},
|
||||
clearSubscriptionsCachePerChannel(state) {
|
||||
state.subscriptionsCachePerChannel = {}
|
||||
clearVideoCache(state) {
|
||||
state.videoCache = {}
|
||||
},
|
||||
updateShortsCacheByChannel(state, { channelId, videos }) {
|
||||
const existingObject = state.shortsCache[channelId]
|
||||
const newObject = existingObject != null ? existingObject : deepCopy(defaultCacheEntryValueForForOneChannel)
|
||||
if (videos != null) { newObject.videos = videos }
|
||||
state.shortsCache[channelId] = newObject
|
||||
},
|
||||
clearShortsCache(state) {
|
||||
state.shortsCache = {}
|
||||
},
|
||||
updateLiveCacheByChannel(state, { channelId, videos }) {
|
||||
const existingObject = state.liveCache[channelId]
|
||||
const newObject = existingObject != null ? existingObject : deepCopy(defaultCacheEntryValueForForOneChannel)
|
||||
if (videos != null) { newObject.videos = videos }
|
||||
state.liveCache[channelId] = newObject
|
||||
},
|
||||
clearLiveCache(state) {
|
||||
state.liveCache = {}
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
|
|
|
@ -317,7 +317,7 @@ const actions = {
|
|||
let urlType = 'unknown'
|
||||
|
||||
const channelPattern =
|
||||
/^\/(?:(?:channel|user|c)\/)?(?<channelId>[^/]+)(?:\/(?<tab>join|featured|videos|shorts|live|streams|playlists|about|community|channels))?\/?$/
|
||||
/^\/(?:(?:channel|user|c)\/)?(?<channelId>[^/]+)(?:\/(?<tab>join|featured|videos|shorts|live|streams|podcasts|releases|playlists|about|community|channels))?\/?$/
|
||||
|
||||
const hashtagPattern = /^\/hashtag\/(?<tag>[^#&/?]+)$/
|
||||
|
||||
|
@ -439,6 +439,12 @@ const actions = {
|
|||
case 'playlists':
|
||||
subPath = 'playlists'
|
||||
break
|
||||
case 'podcasts':
|
||||
subPath = 'podcasts'
|
||||
break
|
||||
case 'releases':
|
||||
subPath = 'releases'
|
||||
break
|
||||
case 'channels':
|
||||
case 'about':
|
||||
subPath = 'about'
|
||||
|
@ -523,7 +529,15 @@ const actions = {
|
|||
|
||||
if (payload.watchProgress > 0 && payload.watchProgress < payload.videoLength - 10) {
|
||||
if (typeof cmdArgs.startOffset === 'string') {
|
||||
args.push(`${cmdArgs.startOffset}${payload.watchProgress}`)
|
||||
if (cmdArgs.startOffset.endsWith('=')) {
|
||||
// For players using `=` in arguments
|
||||
// e.g. vlc --start-time=xxxxx
|
||||
args.push(`${cmdArgs.startOffset}${payload.watchProgress}`)
|
||||
} else {
|
||||
// For players using space in arguments
|
||||
// e.g. smplayer -start xxxxx
|
||||
args.push(cmdArgs.startOffset, Math.trunc(payload.watchProgress))
|
||||
}
|
||||
} else if (!ignoreWarnings) {
|
||||
showExternalPlayerUnsupportedActionToast(externalPlayer, 'starting video at offset')
|
||||
}
|
||||
|
|
|
@ -62,6 +62,8 @@ export default defineComponent({
|
|||
videoContinuationData: null,
|
||||
shortContinuationData: null,
|
||||
liveContinuationData: null,
|
||||
releaseContinuationData: null,
|
||||
podcastContinuationData: null,
|
||||
playlistContinuationData: null,
|
||||
searchContinuationData: null,
|
||||
communityContinuationData: null,
|
||||
|
@ -83,6 +85,8 @@ export default defineComponent({
|
|||
latestVideos: [],
|
||||
latestShorts: [],
|
||||
latestLive: [],
|
||||
latestReleases: [],
|
||||
latestPodcasts: [],
|
||||
latestPlaylists: [],
|
||||
latestCommunityPosts: [],
|
||||
searchResults: [],
|
||||
|
@ -104,6 +108,28 @@ export default defineComponent({
|
|||
playlistSelectValues: [
|
||||
'newest',
|
||||
'last'
|
||||
],
|
||||
|
||||
autoRefreshOnSortByChangeEnabled: false,
|
||||
supportedChannelTabs: [
|
||||
'videos',
|
||||
'shorts',
|
||||
'live',
|
||||
'releases',
|
||||
'podcasts',
|
||||
'playlists',
|
||||
'community',
|
||||
'about'
|
||||
],
|
||||
channelTabs: [
|
||||
'videos',
|
||||
'shorts',
|
||||
'live',
|
||||
'releases',
|
||||
'podcasts',
|
||||
'playlists',
|
||||
'community',
|
||||
'about'
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@ -183,6 +209,10 @@ export default defineComponent({
|
|||
return !isNullOrEmpty(this.shortContinuationData)
|
||||
case 'live':
|
||||
return !isNullOrEmpty(this.liveContinuationData)
|
||||
case 'releases':
|
||||
return !isNullOrEmpty(this.releaseContinuationData)
|
||||
case 'podcasts':
|
||||
return !isNullOrEmpty(this.podcastContinuationData)
|
||||
case 'playlists':
|
||||
return !isNullOrEmpty(this.playlistContinuationData)
|
||||
case 'community':
|
||||
|
@ -209,6 +239,14 @@ export default defineComponent({
|
|||
return this.$store.getters.getHideLiveStreams
|
||||
},
|
||||
|
||||
hideChannelPodcasts: function() {
|
||||
return this.$store.getters.getHideChannelPodcasts
|
||||
},
|
||||
|
||||
hideChannelReleases: function() {
|
||||
return this.$store.getters.getHideChannelReleases
|
||||
},
|
||||
|
||||
hideChannelPlaylists: function() {
|
||||
return this.$store.getters.getHideChannelPlaylists
|
||||
},
|
||||
|
@ -218,36 +256,40 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
tabInfoValues: function () {
|
||||
const values = [
|
||||
'videos',
|
||||
'shorts',
|
||||
'live',
|
||||
'playlists',
|
||||
'community',
|
||||
'about'
|
||||
]
|
||||
const values = [...this.channelTabs]
|
||||
|
||||
const indexToRemove = []
|
||||
// remove tabs from the array based on user settings
|
||||
if (this.hideChannelShorts) {
|
||||
const index = values.indexOf('shorts')
|
||||
values.splice(index, 1)
|
||||
indexToRemove.push(values.indexOf('shorts'))
|
||||
}
|
||||
|
||||
if (this.hideLiveStreams) {
|
||||
const index = values.indexOf('live')
|
||||
values.splice(index, 1)
|
||||
indexToRemove.push(values.indexOf('live'))
|
||||
}
|
||||
|
||||
if (this.hideChannelPlaylists) {
|
||||
const index = values.indexOf('playlists')
|
||||
values.splice(index, 1)
|
||||
indexToRemove.push(values.indexOf('playlists'))
|
||||
}
|
||||
|
||||
if (this.hideChannelCommunity) {
|
||||
const index = values.indexOf('community')
|
||||
values.splice(index, 1)
|
||||
indexToRemove.push(values.indexOf('community'))
|
||||
}
|
||||
|
||||
if (this.hideChannelPodcasts) {
|
||||
indexToRemove.push(values.indexOf('podcasts'))
|
||||
}
|
||||
|
||||
if (this.hideChannelReleases) {
|
||||
indexToRemove.push(values.indexOf('releases'))
|
||||
}
|
||||
|
||||
indexToRemove.forEach(index => {
|
||||
if (index !== -1) {
|
||||
values.splice(index, 1)
|
||||
}
|
||||
})
|
||||
|
||||
return values
|
||||
}
|
||||
},
|
||||
|
@ -261,6 +303,9 @@ export default defineComponent({
|
|||
return
|
||||
}
|
||||
|
||||
// Disable auto refresh on sort value change during state reset
|
||||
this.autoRefreshOnSortByChangeEnabled = false
|
||||
|
||||
this.id = this.$route.params.id
|
||||
this.searchPage = 2
|
||||
this.relatedChannels = []
|
||||
|
@ -272,6 +317,8 @@ export default defineComponent({
|
|||
this.liveSortBy = 'newest'
|
||||
this.playlistSortBy = 'newest'
|
||||
this.latestPlaylists = []
|
||||
this.latestPodcasts = []
|
||||
this.latestReleases = []
|
||||
this.latestCommunityPosts = []
|
||||
this.searchResults = []
|
||||
this.shownElementList = []
|
||||
|
@ -281,6 +328,8 @@ export default defineComponent({
|
|||
this.shortContinuationData = null
|
||||
this.liveContinuationData = null
|
||||
this.playlistContinuationData = null
|
||||
this.podcastContinuationData = null
|
||||
this.releaseContinuationData = null
|
||||
this.searchContinuationData = null
|
||||
this.communityContinuationData = null
|
||||
this.showSearchBar = true
|
||||
|
@ -300,14 +349,20 @@ export default defineComponent({
|
|||
this.showShareMenu = true
|
||||
this.errorMessage = ''
|
||||
|
||||
// Re-enable auto refresh on sort value change AFTER update done
|
||||
if (!process.env.IS_ELECTRON || this.backendPreference === 'invidious') {
|
||||
this.getChannelInfoInvidious()
|
||||
this.autoRefreshOnSortByChangeEnabled = true
|
||||
} else {
|
||||
this.getChannelLocal()
|
||||
this.getChannelLocal().finally(() => {
|
||||
this.autoRefreshOnSortByChangeEnabled = true
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
videoSortBy () {
|
||||
if (!this.autoRefreshOnSortByChangeEnabled) { return }
|
||||
|
||||
this.isElementListLoading = true
|
||||
this.latestVideos = []
|
||||
switch (this.apiUsed) {
|
||||
|
@ -323,6 +378,8 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
shortSortBy() {
|
||||
if (!this.autoRefreshOnSortByChangeEnabled) { return }
|
||||
|
||||
this.isElementListLoading = true
|
||||
this.latestShorts = []
|
||||
switch (this.apiUsed) {
|
||||
|
@ -338,6 +395,8 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
liveSortBy () {
|
||||
if (!this.autoRefreshOnSortByChangeEnabled) { return }
|
||||
|
||||
this.isElementListLoading = true
|
||||
this.latestLive = []
|
||||
switch (this.apiUsed) {
|
||||
|
@ -353,6 +412,8 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
playlistSortBy () {
|
||||
if (!this.autoRefreshOnSortByChangeEnabled) { return }
|
||||
|
||||
this.isElementListLoading = true
|
||||
this.latestPlaylists = []
|
||||
this.playlistContinuationData = null
|
||||
|
@ -386,10 +447,14 @@ export default defineComponent({
|
|||
return
|
||||
}
|
||||
|
||||
// Enable auto refresh on sort value change AFTER initial update done
|
||||
if (!process.env.IS_ELECTRON || this.backendPreference === 'invidious') {
|
||||
this.getChannelInfoInvidious()
|
||||
this.autoRefreshOnSortByChangeEnabled = true
|
||||
} else {
|
||||
this.getChannelLocal()
|
||||
this.getChannelLocal().finally(() => {
|
||||
this.autoRefreshOnSortByChangeEnabled = true
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -593,27 +658,48 @@ export default defineComponent({
|
|||
this.joined = 0
|
||||
this.location = null
|
||||
}
|
||||
const tabs = ['about']
|
||||
|
||||
if (channel.has_videos) {
|
||||
tabs.push('videos')
|
||||
this.getChannelVideosLocal()
|
||||
}
|
||||
|
||||
if (!this.hideChannelShorts && channel.has_shorts) {
|
||||
tabs.push('shorts')
|
||||
this.getChannelShortsLocal()
|
||||
}
|
||||
|
||||
if (!this.hideLiveStreams && channel.has_live_streams) {
|
||||
tabs.push('live')
|
||||
this.getChannelLiveLocal()
|
||||
}
|
||||
|
||||
if (!this.hideChannelPodcasts && channel.has_podcasts) {
|
||||
tabs.push('podcasts')
|
||||
this.getChannelPodcastsLocal()
|
||||
}
|
||||
|
||||
if (!this.hideChannelReleases && channel.has_releases) {
|
||||
tabs.push('releases')
|
||||
this.getChannelReleasesLocal()
|
||||
}
|
||||
|
||||
if (!this.hideChannelPlaylists && channel.has_playlists) {
|
||||
tabs.push('playlists')
|
||||
this.getChannelPlaylistsLocal()
|
||||
}
|
||||
|
||||
if (!this.hideChannelCommunity && channel.has_community) {
|
||||
tabs.push('community')
|
||||
this.getCommunityPostsLocal()
|
||||
}
|
||||
|
||||
this.channelTabs = this.supportedChannelTabs.filter(tab => {
|
||||
return tabs.includes(tab)
|
||||
})
|
||||
|
||||
this.currentTab = this.currentOrFirstTab(this.$route.params.currentTab)
|
||||
this.showSearchBar = channel.has_search
|
||||
|
||||
this.isLoading = false
|
||||
|
@ -882,6 +968,19 @@ export default defineComponent({
|
|||
// some channels only have a few tabs
|
||||
// here are all possible values: home, videos, shorts, streams, playlists, community, channels, about
|
||||
|
||||
const tabs = response.tabs.map(tab => {
|
||||
if (tab === 'streams') {
|
||||
return 'live'
|
||||
}
|
||||
return tab
|
||||
})
|
||||
|
||||
this.channelTabs = this.supportedChannelTabs.filter(tab => {
|
||||
return tabs.includes(tab)
|
||||
})
|
||||
|
||||
this.currentTab = this.currentOrFirstTab(this.$route.params.currentTab)
|
||||
|
||||
if (response.tabs.includes('videos')) {
|
||||
this.channelInvidiousVideos()
|
||||
}
|
||||
|
@ -894,6 +993,14 @@ export default defineComponent({
|
|||
this.channelInvidiousLive()
|
||||
}
|
||||
|
||||
if (!this.hideChannelPodcasts && response.tabs.includes('podcasts')) {
|
||||
this.channelInvidiousPodcasts()
|
||||
}
|
||||
|
||||
if (!this.hideChannelReleases && response.tabs.includes('releases')) {
|
||||
this.channelInvidiousReleases()
|
||||
}
|
||||
|
||||
if (!this.hideChannelPlaylists && response.tabs.includes('playlists')) {
|
||||
this.getPlaylistsInvidious()
|
||||
}
|
||||
|
@ -921,8 +1028,9 @@ export default defineComponent({
|
|||
|
||||
channelInvidiousVideos: function (sortByChanged) {
|
||||
const payload = {
|
||||
resource: 'channels/videos',
|
||||
resource: 'channels',
|
||||
id: this.id,
|
||||
subResource: 'videos',
|
||||
params: {
|
||||
sort_by: this.videoSortBy,
|
||||
}
|
||||
|
@ -1128,7 +1236,8 @@ export default defineComponent({
|
|||
getPlaylistsInvidious: function () {
|
||||
this.isElementListLoading = true
|
||||
const payload = {
|
||||
resource: 'channels/playlists',
|
||||
resource: 'channels',
|
||||
subResource: 'playlists',
|
||||
id: this.id,
|
||||
params: {
|
||||
sort_by: this.playlistSortBy
|
||||
|
@ -1164,7 +1273,8 @@ export default defineComponent({
|
|||
}
|
||||
|
||||
const payload = {
|
||||
resource: 'channels/playlists',
|
||||
resource: 'channels',
|
||||
subResource: 'playlists',
|
||||
id: this.id,
|
||||
params: {
|
||||
sort_by: this.playlistSortBy
|
||||
|
@ -1194,6 +1304,232 @@ export default defineComponent({
|
|||
})
|
||||
},
|
||||
|
||||
getChannelReleasesLocal: async function () {
|
||||
this.isElementListLoading = true
|
||||
const expectedId = this.id
|
||||
|
||||
try {
|
||||
/**
|
||||
* @type {import('youtubei.js').YT.Channel}
|
||||
*/
|
||||
const channel = this.channelInstance
|
||||
const releaseTab = await channel.getReleases()
|
||||
|
||||
if (expectedId !== this.id) {
|
||||
return
|
||||
}
|
||||
|
||||
this.latestReleases = releaseTab.playlists.map(playlist => parseLocalListPlaylist(playlist, channel.header.author))
|
||||
this.releaseContinuationData = releaseTab.has_continuation ? releaseTab : null
|
||||
this.isElementListLoading = false
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
const errorMessage = this.$t('Local API Error (Click to copy)')
|
||||
showToast(`${errorMessage}: ${err}`, 10000, () => {
|
||||
copyToClipboard(err)
|
||||
})
|
||||
if (this.backendPreference === 'local' && this.backendFallback) {
|
||||
showToast(this.$t('Falling back to Invidious API'))
|
||||
this.getChannelReleasesInvidious()
|
||||
} else {
|
||||
this.isLoading = false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getChannelReleasesLocalMore: async function () {
|
||||
try {
|
||||
/**
|
||||
* @type {import('youtubei.js').YT.ChannelListContinuation}
|
||||
*/
|
||||
const continuation = await this.releaseContinuationData.getContinuation()
|
||||
|
||||
const parsedReleases = continuation.playlists.map(playlist => parseLocalListPlaylist(playlist, this.channelInstance.header.author))
|
||||
this.latestReleases = this.latestReleases.concat(parsedReleases)
|
||||
this.releaseContinuationData = continuation.has_continuation ? continuation : null
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
const errorMessage = this.$t('Local API Error (Click to copy)')
|
||||
showToast(`${errorMessage}: ${err}`, 10000, () => {
|
||||
copyToClipboard(err)
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
channelInvidiousReleases: function() {
|
||||
this.isElementListLoading = true
|
||||
const payload = {
|
||||
resource: 'channels',
|
||||
subResource: 'releases',
|
||||
id: this.id,
|
||||
}
|
||||
|
||||
invidiousAPICall(payload).then((response) => {
|
||||
this.releaseContinuationData = response.continuation || null
|
||||
this.latestReleases = response.playlists
|
||||
this.isElementListLoading = false
|
||||
}).catch(async (err) => {
|
||||
console.error(err)
|
||||
const errorMessage = this.$t('Invidious API Error (Click to copy)')
|
||||
showToast(`${errorMessage}: ${err}`, 10000, () => {
|
||||
copyToClipboard(err)
|
||||
})
|
||||
if (process.env.IS_ELECTRON && this.backendPreference === 'invidious' && this.backendFallback) {
|
||||
showToast(this.$t('Falling back to Local API'))
|
||||
if (!this.channelInstance) {
|
||||
this.channelInstance = await getLocalChannel(this.id)
|
||||
}
|
||||
this.getChannelReleasesLocal()
|
||||
} else {
|
||||
this.isLoading = false
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
channelInvidiousReleasesMore: function () {
|
||||
if (this.releaseContinuationData === null) {
|
||||
console.warn('There are no more podcasts available for this channel')
|
||||
return
|
||||
}
|
||||
|
||||
const payload = {
|
||||
resource: 'channels',
|
||||
subResource: 'releases',
|
||||
id: this.id
|
||||
}
|
||||
|
||||
invidiousAPICall(payload).then((response) => {
|
||||
this.releaseContinuationData = response.continuation || null
|
||||
this.latestReleases = this.latestReleases.concat(response.playlists)
|
||||
this.isElementListLoading = false
|
||||
}).catch((err) => {
|
||||
console.error(err)
|
||||
const errorMessage = this.$t('Invidious API Error (Click to copy)')
|
||||
showToast(`${errorMessage}: ${err}`, 10000, () => {
|
||||
copyToClipboard(err)
|
||||
})
|
||||
if (process.env.IS_ELECTRON && this.backendPreference === 'invidious' && this.backendFallback) {
|
||||
showToast(this.$t('Falling back to Local API'))
|
||||
this.getChannelLocal()
|
||||
} else {
|
||||
this.isLoading = false
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
getChannelPodcastsLocal: async function () {
|
||||
this.isElementListLoading = true
|
||||
const expectedId = this.id
|
||||
|
||||
try {
|
||||
/**
|
||||
* @type {import('youtubei.js').YT.Channel}
|
||||
*/
|
||||
const channel = this.channelInstance
|
||||
const podcastTab = await channel.getPodcasts()
|
||||
|
||||
if (expectedId !== this.id) {
|
||||
return
|
||||
}
|
||||
|
||||
this.latestPodcasts = podcastTab.playlists.map(playlist => parseLocalListPlaylist(playlist, channel.header.author))
|
||||
this.podcastContinuationData = podcastTab.has_continuation ? podcastTab : null
|
||||
this.isElementListLoading = false
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
const errorMessage = this.$t('Local API Error (Click to copy)')
|
||||
showToast(`${errorMessage}: ${err}`, 10000, () => {
|
||||
copyToClipboard(err)
|
||||
})
|
||||
if (this.backendPreference === 'local' && this.backendFallback) {
|
||||
showToast(this.$t('Falling back to Invidious API'))
|
||||
this.getChannelPodcastsInvidious()
|
||||
} else {
|
||||
this.isLoading = false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getChannelPodcastsLocalMore: async function () {
|
||||
try {
|
||||
/**
|
||||
* @type {import('youtubei.js').YT.ChannelListContinuation}
|
||||
*/
|
||||
const continuation = await this.podcastContinuationData.getContinuation()
|
||||
|
||||
const parsedPodcasts = continuation.playlists.map(playlist => parseLocalListPlaylist(playlist, this.channelInstance.header.author))
|
||||
this.latestPodcasts = this.latestPodcasts.concat(parsedPodcasts)
|
||||
this.releaseContinuationData = continuation.has_continuation ? continuation : null
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
const errorMessage = this.$t('Local API Error (Click to copy)')
|
||||
showToast(`${errorMessage}: ${err}`, 10000, () => {
|
||||
copyToClipboard(err)
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
channelInvidiousPodcasts: function() {
|
||||
this.isElementListLoading = true
|
||||
const payload = {
|
||||
resource: 'channels',
|
||||
subResource: 'podcasts',
|
||||
id: this.id,
|
||||
}
|
||||
|
||||
invidiousAPICall(payload).then((response) => {
|
||||
this.podcastContinuationData = response.continuation || null
|
||||
this.latestPodcasts = response.playlists
|
||||
this.isElementListLoading = false
|
||||
}).catch(async (err) => {
|
||||
console.error(err)
|
||||
const errorMessage = this.$t('Invidious API Error (Click to copy)')
|
||||
showToast(`${errorMessage}: ${err}`, 10000, () => {
|
||||
copyToClipboard(err)
|
||||
})
|
||||
if (process.env.IS_ELECTRON && this.backendPreference === 'invidious' && this.backendFallback) {
|
||||
showToast(this.$t('Falling back to Local API'))
|
||||
if (!this.channelInstance) {
|
||||
this.channelInstance = await getLocalChannel(this.id)
|
||||
}
|
||||
this.getChannelPodcastsLocal()
|
||||
} else {
|
||||
this.isLoading = false
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
channelInvidiousPodcastsMore: function () {
|
||||
if (this.podcastContinuationData === null) {
|
||||
console.warn('There are no more podcasts available for this channel')
|
||||
return
|
||||
}
|
||||
|
||||
const payload = {
|
||||
resource: 'channels',
|
||||
subResource: 'podcasts',
|
||||
id: this.id
|
||||
}
|
||||
|
||||
invidiousAPICall(payload).then((response) => {
|
||||
this.podcastContinuationData = response.continuation || null
|
||||
this.latestPodcasts = this.latestPodcasts.concat(response.playlists)
|
||||
this.isElementListLoading = false
|
||||
}).catch((err) => {
|
||||
console.error(err)
|
||||
const errorMessage = this.$t('Invidious API Error (Click to copy)')
|
||||
showToast(`${errorMessage}: ${err}`, 10000, () => {
|
||||
copyToClipboard(err)
|
||||
})
|
||||
if (process.env.IS_ELECTRON && this.backendPreference === 'invidious' && this.backendFallback) {
|
||||
showToast(this.$t('Falling back to Local API'))
|
||||
this.getChannelLocal()
|
||||
} else {
|
||||
this.isLoading = false
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
getCommunityPostsLocal: async function () {
|
||||
const expectedId = this.id
|
||||
|
||||
|
@ -1333,6 +1669,12 @@ export default defineComponent({
|
|||
break
|
||||
}
|
||||
break
|
||||
case 'releases':
|
||||
this.getChannelReleasesLocalMore()
|
||||
break
|
||||
case 'podcasts':
|
||||
this.getChannelPodcastsLocalMore()
|
||||
break
|
||||
case 'playlists':
|
||||
switch (this.apiUsed) {
|
||||
case 'local':
|
||||
|
@ -1470,8 +1812,9 @@ export default defineComponent({
|
|||
|
||||
searchChannelInvidious: function () {
|
||||
const payload = {
|
||||
resource: 'channels/search',
|
||||
resource: 'channels',
|
||||
id: this.id,
|
||||
subResource: 'search',
|
||||
params: {
|
||||
q: this.lastSearchQuery,
|
||||
page: this.searchPage
|
||||
|
|
|
@ -86,6 +86,7 @@
|
|||
:aria-label="$t('Channel.Channel Tabs')"
|
||||
>
|
||||
<div
|
||||
v-if="tabInfoValues.includes('videos')"
|
||||
id="videosTab"
|
||||
class="tab"
|
||||
:class="(currentTab==='videos')?'selectedTab':''"
|
||||
|
@ -99,7 +100,7 @@
|
|||
{{ $t("Channel.Videos.Videos").toUpperCase() }}
|
||||
</div>
|
||||
<div
|
||||
v-if="!hideChannelShorts"
|
||||
v-if="tabInfoValues.includes('shorts') && !hideChannelShorts"
|
||||
id="shortsTab"
|
||||
class="tab"
|
||||
:class="(currentTab==='shorts')?'selectedTab':''"
|
||||
|
@ -110,10 +111,10 @@
|
|||
@click="changeTab('shorts')"
|
||||
@keydown.left.right.enter.space="changeTab('shorts', $event)"
|
||||
>
|
||||
{{ $t("Channel.Shorts.Shorts").toUpperCase() }}
|
||||
{{ $t("Global.Shorts").toUpperCase() }}
|
||||
</div>
|
||||
<div
|
||||
v-if="!hideLiveStreams"
|
||||
v-if="tabInfoValues.includes('live') && !hideLiveStreams"
|
||||
id="liveTab"
|
||||
class="tab"
|
||||
:class="(currentTab==='live')?'selectedTab':''"
|
||||
|
@ -127,7 +128,35 @@
|
|||
{{ $t("Channel.Live.Live").toUpperCase() }}
|
||||
</div>
|
||||
<div
|
||||
v-if="!hideChannelPlaylists"
|
||||
v-if="tabInfoValues.includes('releases') && !hideChannelReleases"
|
||||
id="releasesTab"
|
||||
class="tab"
|
||||
role="tab"
|
||||
:aria-selected="String(currentTab === 'releases')"
|
||||
aria-controls="releasePanel"
|
||||
:tabindex="currentTab === 'releases' ? 0 : -1"
|
||||
:class="(currentTab==='releases')?'selectedTab':''"
|
||||
@click="changeTab('releases')"
|
||||
@keydown.left.right.enter.space="changeTab('releases', $event)"
|
||||
>
|
||||
{{ $t("Channel.Releases.Releases").toUpperCase() }}
|
||||
</div>
|
||||
<div
|
||||
v-if="tabInfoValues.includes('podcasts') && !hideChannelPodcasts"
|
||||
id="podcastsTab"
|
||||
class="tab"
|
||||
role="tab"
|
||||
:aria-selected="String(currentTab === 'podcasts')"
|
||||
aria-controls="podcastPanel"
|
||||
:tabindex="currentTab === 'podcasts' ? 0 : -1"
|
||||
:class="(currentTab==='podcasts')?'selectedTab':''"
|
||||
@click="changeTab('podcasts')"
|
||||
@keydown.left.right.enter.space="changeTab('podcasts', $event)"
|
||||
>
|
||||
{{ $t("Channel.Podcasts.Podcasts").toUpperCase() }}
|
||||
</div>
|
||||
<div
|
||||
v-if="tabInfoValues.includes('playlists') && !hideChannelPlaylists"
|
||||
id="playlistsTab"
|
||||
class="tab"
|
||||
role="tab"
|
||||
|
@ -141,7 +170,7 @@
|
|||
{{ $t("Channel.Playlists.Playlists").toUpperCase() }}
|
||||
</div>
|
||||
<div
|
||||
v-if="!hideChannelCommunity"
|
||||
v-if="tabInfoValues.includes('community') && !hideChannelCommunity"
|
||||
id="communityTab"
|
||||
class="tab"
|
||||
role="tab"
|
||||
|
@ -244,6 +273,7 @@
|
|||
v-show="currentTab === 'videos'"
|
||||
id="videoPanel"
|
||||
:data="latestVideos"
|
||||
:use-channels-hidden-preference="false"
|
||||
role="tabpanel"
|
||||
aria-labelledby="videosTab"
|
||||
/>
|
||||
|
@ -258,6 +288,7 @@
|
|||
v-if="!hideChannelShorts && currentTab === 'shorts'"
|
||||
id="shortPanel"
|
||||
:data="latestShorts"
|
||||
:use-channels-hidden-preference="false"
|
||||
role="tabpanel"
|
||||
aria-labelledby="shortsTab"
|
||||
/>
|
||||
|
@ -273,6 +304,7 @@
|
|||
v-show="currentTab === 'live'"
|
||||
id="livePanel"
|
||||
:data="latestLive"
|
||||
:use-channels-hidden-preference="false"
|
||||
role="tabpanel"
|
||||
aria-labelledby="liveTab"
|
||||
/>
|
||||
|
@ -283,10 +315,41 @@
|
|||
{{ $t("Channel.Live.This channel does not currently have any live streams") }}
|
||||
</p>
|
||||
</ft-flex-box>
|
||||
<ft-element-list
|
||||
v-if="!hideChannelPodcasts && currentTab === 'podcasts'"
|
||||
id="podcastPanel"
|
||||
:data="latestPodcasts"
|
||||
:use-channels-hidden-preference="false"
|
||||
role="tabpanel"
|
||||
aria-labelledby="podcastsTab"
|
||||
/>
|
||||
<ft-flex-box
|
||||
v-if="!hideChannelPodcasts && currentTab === 'podcasts' && latestPodcasts.length === 0"
|
||||
>
|
||||
<p class="message">
|
||||
{{ $t("Channel.Podcasts.This channel does not currently have any podcasts") }}
|
||||
</p>
|
||||
</ft-flex-box>
|
||||
<ft-element-list
|
||||
v-if="!hideChannelReleases && currentTab === 'releases'"
|
||||
id="releasePanel"
|
||||
:data="latestReleases"
|
||||
:use-channels-hidden-preference="false"
|
||||
role="tabpanel"
|
||||
aria-labelledby="releasesTab"
|
||||
/>
|
||||
<ft-flex-box
|
||||
v-if="!hideChannelReleases && currentTab === 'releases' && latestReleases.length === 0"
|
||||
>
|
||||
<p class="message">
|
||||
{{ $t("Channel.Releases.This channel does not currently have any releases") }}
|
||||
</p>
|
||||
</ft-flex-box>
|
||||
<ft-element-list
|
||||
v-if="!hideChannelPlaylists && currentTab === 'playlists'"
|
||||
id="playlistPanel"
|
||||
:data="latestPlaylists"
|
||||
:use-channels-hidden-preference="false"
|
||||
role="tabpanel"
|
||||
aria-labelledby="playlistsTab"
|
||||
/>
|
||||
|
@ -301,6 +364,7 @@
|
|||
v-if="!hideChannelCommunity && currentTab === 'community'"
|
||||
id="communityPanel"
|
||||
:data="latestCommunityPosts"
|
||||
:use-channels-hidden-preference="false"
|
||||
role="tabpanel"
|
||||
aria-labelledby="communityTab"
|
||||
display="list"
|
||||
|
@ -315,6 +379,7 @@
|
|||
<ft-element-list
|
||||
v-show="currentTab === 'search'"
|
||||
:data="searchResults"
|
||||
:use-channels-hidden-preference="false"
|
||||
/>
|
||||
<ft-flex-box
|
||||
v-if="currentTab === 'search' && searchResults.length === 0"
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
v-if="activeData.length > 0 && !isLoading"
|
||||
:data="activeData"
|
||||
:show-video-with-last-viewed-playlist="true"
|
||||
:use-channels-hidden-preference="false"
|
||||
/>
|
||||
<ft-flex-box
|
||||
v-if="showLoadMoreButton"
|
||||
|
|
|
@ -118,10 +118,6 @@ export default defineComponent({
|
|||
try {
|
||||
const { results, continuationData } = await getLocalSearchResults(payload.query, payload.searchSettings, this.showFamilyFriendlyOnly)
|
||||
|
||||
if (results.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
this.apiUsed = 'local'
|
||||
|
||||
this.shownResults = results
|
||||
|
|
|
@ -8,20 +8,31 @@
|
|||
color: var(--tertiary-text-color);
|
||||
}
|
||||
|
||||
.floatingTopButton {
|
||||
position: fixed;
|
||||
top: 70px;
|
||||
right: 10px;
|
||||
.subscriptionTabs {
|
||||
width: 100%;
|
||||
margin-top: -3px;
|
||||
color: var(--tertiary-text-color);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.channelBubble {
|
||||
display: inline-block;
|
||||
.selectedTab {
|
||||
border-bottom: 3px solid var(--primary-color);
|
||||
color: var(--primary-text-color);
|
||||
font-weight: bold;
|
||||
box-sizing: border-box;
|
||||
margin-bottom: -3px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 350px) {
|
||||
.floatingTopButton {
|
||||
position: absolute
|
||||
}
|
||||
.tab {
|
||||
text-align: center;
|
||||
padding: 15px;
|
||||
font-size: 15px;
|
||||
cursor: pointer;
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.tab:hover {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 680px) {
|
||||
|
|
|
@ -1,496 +1,128 @@
|
|||
import { defineComponent } from 'vue'
|
||||
import { mapActions, mapMutations } from 'vuex'
|
||||
import FtLoader from '../../components/ft-loader/ft-loader.vue'
|
||||
import FtCard from '../../components/ft-card/ft-card.vue'
|
||||
import FtButton from '../../components/ft-button/ft-button.vue'
|
||||
import FtIconButton from '../../components/ft-icon-button/ft-icon-button.vue'
|
||||
import FtFlexBox from '../../components/ft-flex-box/ft-flex-box.vue'
|
||||
import FtElementList from '../../components/ft-element-list/ft-element-list.vue'
|
||||
import FtChannelBubble from '../../components/ft-channel-bubble/ft-channel-bubble.vue'
|
||||
|
||||
import { calculatePublishedDate, copyToClipboard, showToast } from '../../helpers/utils'
|
||||
import { invidiousAPICall } from '../../helpers/api/invidious'
|
||||
import { getLocalChannelVideos } from '../../helpers/api/local'
|
||||
import SubscriptionsVideos from '../../components/subscriptions-videos/subscriptions-videos.vue'
|
||||
import SubscriptionsLive from '../../components/subscriptions-live/subscriptions-live.vue'
|
||||
import SubscriptionsShorts from '../../components/subscriptions-shorts/subscriptions-shorts.vue'
|
||||
|
||||
import FtCard from '../../components/ft-card/ft-card.vue'
|
||||
import FtFlexBox from '../../components/ft-flex-box/ft-flex-box.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Subscriptions',
|
||||
components: {
|
||||
'ft-loader': FtLoader,
|
||||
'subscriptions-videos': SubscriptionsVideos,
|
||||
'subscriptions-live': SubscriptionsLive,
|
||||
'subscriptions-shorts': SubscriptionsShorts,
|
||||
'ft-card': FtCard,
|
||||
'ft-button': FtButton,
|
||||
'ft-icon-button': FtIconButton,
|
||||
'ft-flex-box': FtFlexBox,
|
||||
'ft-element-list': FtElementList,
|
||||
'ft-channel-bubble': FtChannelBubble
|
||||
'ft-flex-box': FtFlexBox
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
isLoading: false,
|
||||
dataLimit: 100,
|
||||
videoList: [],
|
||||
errorChannels: [],
|
||||
attemptedFetch: false,
|
||||
currentTab: 'videos'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
backendPreference: function () {
|
||||
let preference = this.$store.getters.getBackendPreference
|
||||
if (preference === 'piped') {
|
||||
preference = this.$store.getters.getFallbackPreference
|
||||
hideSubscriptionsVideos: function () {
|
||||
return this.$store.getters.getHideSubscriptionsVideos
|
||||
},
|
||||
hideSubscriptionsShorts: function () {
|
||||
return this.$store.getters.getHideSubscriptionsShorts
|
||||
},
|
||||
hideSubscriptionsLive: function () {
|
||||
return this.$store.getters.getHideLiveStreams || this.$store.getters.getHideSubscriptionsLive
|
||||
},
|
||||
visibleTabs: function () {
|
||||
const tabs = []
|
||||
|
||||
if (!this.hideSubscriptionsVideos) {
|
||||
tabs.push('videos')
|
||||
}
|
||||
return preference
|
||||
},
|
||||
|
||||
backendFallback: function () {
|
||||
return this.$store.getters.getBackendFallback && this.$store.getters.getBackendPreference !== 'piped'
|
||||
},
|
||||
|
||||
currentInvidiousInstance: function () {
|
||||
return this.$store.getters.getCurrentInvidiousInstance
|
||||
},
|
||||
|
||||
hideWatchedSubs: function () {
|
||||
return this.$store.getters.getHideWatchedSubs
|
||||
},
|
||||
|
||||
useRssFeeds: function () {
|
||||
return this.$store.getters.getUseRssFeeds
|
||||
},
|
||||
|
||||
activeVideoList: function () {
|
||||
if (this.videoList.length < this.dataLimit) {
|
||||
return this.videoList
|
||||
} else {
|
||||
return this.videoList.slice(0, this.dataLimit)
|
||||
if (!this.hideSubscriptionsShorts) {
|
||||
tabs.push('shorts')
|
||||
}
|
||||
},
|
||||
|
||||
activeProfile: function () {
|
||||
return this.$store.getters.getActiveProfile
|
||||
},
|
||||
activeProfileId: function () {
|
||||
return this.activeProfile._id
|
||||
},
|
||||
if (!this.hideSubscriptionsLive) {
|
||||
tabs.push('live')
|
||||
}
|
||||
|
||||
cacheEntriesForAllActiveProfileChannels() {
|
||||
const entries = []
|
||||
this.activeSubscriptionList.forEach((channel) => {
|
||||
const cacheEntry = this.$store.getters.getSubscriptionsCacheEntriesForOneChannel(channel.id)
|
||||
if (cacheEntry == null) { return }
|
||||
|
||||
entries.push(cacheEntry)
|
||||
})
|
||||
return entries
|
||||
},
|
||||
videoCacheForAllActiveProfileChannelsPresent() {
|
||||
if (this.cacheEntriesForAllActiveProfileChannels.length === 0) { return false }
|
||||
if (this.cacheEntriesForAllActiveProfileChannels.length < this.activeSubscriptionList.length) { return false }
|
||||
|
||||
return this.cacheEntriesForAllActiveProfileChannels.every((cacheEntry) => {
|
||||
return cacheEntry.videos != null
|
||||
})
|
||||
},
|
||||
|
||||
historyCache: function () {
|
||||
return this.$store.getters.getHistoryCache
|
||||
},
|
||||
|
||||
activeSubscriptionList: function () {
|
||||
return this.activeProfile.subscriptions
|
||||
},
|
||||
|
||||
hideLiveStreams: function() {
|
||||
return this.$store.getters.getHideLiveStreams
|
||||
},
|
||||
|
||||
hideUpcomingPremieres: function () {
|
||||
return this.$store.getters.getHideUpcomingPremieres
|
||||
},
|
||||
|
||||
fetchSubscriptionsAutomatically: function() {
|
||||
return this.$store.getters.getFetchSubscriptionsAutomatically
|
||||
},
|
||||
return tabs
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
activeProfile: async function (_) {
|
||||
this.isLoading = true
|
||||
this.loadVideosFromCacheSometimes()
|
||||
currentTab(value) {
|
||||
if (value !== null) {
|
||||
// Save last used tab, restore when view mounted again
|
||||
sessionStorage.setItem('Subscriptions/currentTab', value)
|
||||
} else {
|
||||
sessionStorage.removeItem('Subscriptions/currentTab')
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted: async function () {
|
||||
document.addEventListener('keydown', this.keyboardShortcutHandler)
|
||||
|
||||
this.isLoading = true
|
||||
const dataLimit = sessionStorage.getItem('subscriptionLimit')
|
||||
if (dataLimit !== null) {
|
||||
this.dataLimit = dataLimit
|
||||
/**
|
||||
* @param {string[]} newValue
|
||||
*/
|
||||
visibleTabs: function (newValue) {
|
||||
if (newValue.length === 0) {
|
||||
this.currentTab = null
|
||||
} else if (!newValue.includes(this.currentTab)) {
|
||||
this.currentTab = newValue[0]
|
||||
}
|
||||
}
|
||||
|
||||
this.loadVideosFromCacheSometimes()
|
||||
},
|
||||
beforeDestroy: function () {
|
||||
document.removeEventListener('keydown', this.keyboardShortcutHandler)
|
||||
created: async function () {
|
||||
if (this.visibleTabs.length === 0) {
|
||||
this.currentTab = null
|
||||
} else {
|
||||
// Restore currentTab
|
||||
const lastCurrentTabId = sessionStorage.getItem('Subscriptions/currentTab')
|
||||
if (lastCurrentTabId !== null) { this.changeTab(lastCurrentTabId) }
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
loadVideosFromCacheSometimes() {
|
||||
// This method is called on view visible
|
||||
if (this.videoCacheForAllActiveProfileChannelsPresent) {
|
||||
this.loadVideosFromCacheForAllActiveProfileChannels()
|
||||
changeTab: function (tab) {
|
||||
if (tab === this.currentTab) {
|
||||
return
|
||||
}
|
||||
|
||||
this.maybeLoadVideosForSubscriptionsFromRemote()
|
||||
},
|
||||
|
||||
async loadVideosFromCacheForAllActiveProfileChannels() {
|
||||
const videoList = []
|
||||
this.activeSubscriptionList.forEach((channel) => {
|
||||
const channelCacheEntry = this.$store.getters.getSubscriptionsCacheEntriesForOneChannel(channel.id)
|
||||
|
||||
videoList.push(...channelCacheEntry.videos)
|
||||
})
|
||||
this.updateVideoListAfterProcessing(videoList)
|
||||
this.isLoading = false
|
||||
},
|
||||
|
||||
goToChannel: function (id) {
|
||||
this.$router.push({ path: `/channel/${id}` })
|
||||
},
|
||||
|
||||
loadVideosForSubscriptionsFromRemote: async function () {
|
||||
if (this.activeSubscriptionList.length === 0) {
|
||||
this.isLoading = false
|
||||
this.videoList = []
|
||||
return
|
||||
}
|
||||
|
||||
const channelsToLoadFromRemote = this.activeSubscriptionList
|
||||
const videoList = []
|
||||
let channelCount = 0
|
||||
this.isLoading = true
|
||||
|
||||
let useRss = this.useRssFeeds
|
||||
if (channelsToLoadFromRemote.length >= 125 && !useRss) {
|
||||
showToast(
|
||||
this.$t('Subscriptions["This profile has a large number of subscriptions. Forcing RSS to avoid rate limiting"]'),
|
||||
10000
|
||||
)
|
||||
useRss = true
|
||||
}
|
||||
this.updateShowProgressBar(true)
|
||||
this.setProgressBarPercentage(0)
|
||||
this.attemptedFetch = true
|
||||
|
||||
this.errorChannels = []
|
||||
const videoListFromRemote = (await Promise.all(channelsToLoadFromRemote.map(async (channel) => {
|
||||
let videos = []
|
||||
if (!process.env.IS_ELECTRON || this.backendPreference === 'invidious') {
|
||||
if (useRss) {
|
||||
videos = await this.getChannelVideosInvidiousRSS(channel)
|
||||
} else {
|
||||
videos = await this.getChannelVideosInvidiousScraper(channel)
|
||||
}
|
||||
} else {
|
||||
if (useRss) {
|
||||
videos = await this.getChannelVideosLocalRSS(channel)
|
||||
} else {
|
||||
videos = await this.getChannelVideosLocalScraper(channel)
|
||||
}
|
||||
}
|
||||
|
||||
channelCount++
|
||||
const percentageComplete = (channelCount / channelsToLoadFromRemote.length) * 100
|
||||
this.setProgressBarPercentage(percentageComplete)
|
||||
this.updateSubscriptionsCacheForOneChannel({
|
||||
channelId: channel.id,
|
||||
videos: videos,
|
||||
})
|
||||
return videos
|
||||
}))).flatMap((o) => o)
|
||||
videoList.push(...videoListFromRemote)
|
||||
|
||||
this.updateVideoListAfterProcessing(videoList)
|
||||
this.isLoading = false
|
||||
this.updateShowProgressBar(false)
|
||||
},
|
||||
|
||||
updateVideoListAfterProcessing(videoList) {
|
||||
// Filtering and sorting based in preference
|
||||
videoList.sort((a, b) => {
|
||||
return b.publishedDate - a.publishedDate
|
||||
})
|
||||
if (this.hideLiveStreams) {
|
||||
videoList = videoList.filter(item => {
|
||||
return (!item.liveNow && !item.isUpcoming)
|
||||
})
|
||||
}
|
||||
if (this.hideUpcomingPremieres) {
|
||||
videoList = videoList.filter(item => {
|
||||
if (item.isRSS) {
|
||||
// viewCount is our only method of detecting premieres in RSS
|
||||
// data without sending an additional request.
|
||||
// If we ever get a better flag, use it here instead.
|
||||
return item.viewCount !== '0'
|
||||
}
|
||||
// Observed for premieres in Local API Subscriptions.
|
||||
return item.premiereDate == null
|
||||
})
|
||||
}
|
||||
|
||||
this.videoList = videoList.filter((video) => {
|
||||
if (this.hideWatchedSubs) {
|
||||
const historyIndex = this.historyCache.findIndex((x) => {
|
||||
return x.videoId === video.videoId
|
||||
})
|
||||
|
||||
return historyIndex === -1
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
maybeLoadVideosForSubscriptionsFromRemote: async function () {
|
||||
if (this.fetchSubscriptionsAutomatically) {
|
||||
// `this.isLoading = false` is called inside `loadVideosForSubscriptionsFromRemote` when needed
|
||||
await this.loadVideosForSubscriptionsFromRemote()
|
||||
if (this.visibleTabs.includes(tab)) {
|
||||
this.currentTab = tab
|
||||
} else {
|
||||
this.videoList = []
|
||||
this.attemptedFetch = false
|
||||
this.isLoading = false
|
||||
this.currentTab = null
|
||||
}
|
||||
},
|
||||
|
||||
getChannelVideosLocalScraper: async function (channel, failedAttempts = 0) {
|
||||
try {
|
||||
const videos = await getLocalChannelVideos(channel.id)
|
||||
|
||||
if (videos === null) {
|
||||
this.errorChannels.push(channel)
|
||||
return []
|
||||
}
|
||||
|
||||
videos.map(video => {
|
||||
if (video.liveNow) {
|
||||
video.publishedDate = new Date().getTime()
|
||||
} else if (video.isUpcoming) {
|
||||
video.publishedDate = video.premiereDate
|
||||
} else {
|
||||
video.publishedDate = calculatePublishedDate(video.publishedText)
|
||||
}
|
||||
return video
|
||||
})
|
||||
|
||||
return videos
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
const errorMessage = this.$t('Local API Error (Click to copy)')
|
||||
showToast(`${errorMessage}: ${err}`, 10000, () => {
|
||||
copyToClipboard(err)
|
||||
})
|
||||
switch (failedAttempts) {
|
||||
case 0:
|
||||
return await this.getChannelVideosLocalRSS(channel, failedAttempts + 1)
|
||||
case 1:
|
||||
if (this.backendFallback) {
|
||||
showToast(this.$t('Falling back to Invidious API'))
|
||||
return await this.getChannelVideosInvidiousScraper(channel, failedAttempts + 1)
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
case 2:
|
||||
return await this.getChannelVideosLocalRSS(channel, failedAttempts + 1)
|
||||
default:
|
||||
return []
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getChannelVideosLocalRSS: async function (channel, failedAttempts = 0) {
|
||||
const feedUrl = `https://www.youtube.com/feeds/videos.xml?channel_id=${channel.id}`
|
||||
|
||||
try {
|
||||
const response = await fetch(feedUrl)
|
||||
|
||||
if (response.status === 404) {
|
||||
this.errorChannels.push(channel)
|
||||
return []
|
||||
}
|
||||
|
||||
return await this.parseYouTubeRSSFeed(await response.text(), channel.id)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
const errorMessage = this.$t('Local API Error (Click to copy)')
|
||||
showToast(`${errorMessage}: ${error}`, 10000, () => {
|
||||
copyToClipboard(error)
|
||||
})
|
||||
switch (failedAttempts) {
|
||||
case 0:
|
||||
return this.getChannelVideosLocalScraper(channel, failedAttempts + 1)
|
||||
case 1:
|
||||
if (this.backendFallback) {
|
||||
showToast(this.$t('Falling back to Invidious API'))
|
||||
return this.getChannelVideosInvidiousRSS(channel, failedAttempts + 1)
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
case 2:
|
||||
return this.getChannelVideosLocalScraper(channel, failedAttempts + 1)
|
||||
default:
|
||||
return []
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getChannelVideosInvidiousScraper: function (channel, failedAttempts = 0) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const subscriptionsPayload = {
|
||||
resource: 'channels/latest',
|
||||
id: channel.id,
|
||||
params: {}
|
||||
}
|
||||
|
||||
invidiousAPICall(subscriptionsPayload).then(async (result) => {
|
||||
resolve(await Promise.all(result.videos.map((video) => {
|
||||
if (video.liveNow) {
|
||||
video.publishedDate = new Date().getTime()
|
||||
} else if (video.isUpcoming) {
|
||||
video.publishedDate = new Date(video.premiereTimestamp * 1000)
|
||||
} else {
|
||||
video.publishedDate = new Date(video.published * 1000)
|
||||
}
|
||||
return video
|
||||
})))
|
||||
}).catch((err) => {
|
||||
console.error(err)
|
||||
const errorMessage = this.$t('Invidious API Error (Click to copy)')
|
||||
showToast(`${errorMessage}: ${err.responseText}`, 10000, () => {
|
||||
copyToClipboard(err.responseText)
|
||||
})
|
||||
switch (failedAttempts) {
|
||||
case 0:
|
||||
resolve(this.getChannelVideosInvidiousRSS(channel, failedAttempts + 1))
|
||||
break
|
||||
case 1:
|
||||
if (process.env.IS_ELECTRON && this.backendFallback) {
|
||||
showToast(this.$t('Falling back to the local API'))
|
||||
resolve(this.getChannelVideosLocalScraper(channel, failedAttempts + 1))
|
||||
} else {
|
||||
resolve([])
|
||||
}
|
||||
break
|
||||
case 2:
|
||||
resolve(this.getChannelVideosInvidiousRSS(channel, failedAttempts + 1))
|
||||
break
|
||||
default:
|
||||
resolve([])
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
getChannelVideosInvidiousRSS: async function (channel, failedAttempts = 0) {
|
||||
const feedUrl = `${this.currentInvidiousInstance}/feed/channel/${channel.id}`
|
||||
|
||||
try {
|
||||
const response = await fetch(feedUrl)
|
||||
|
||||
if (response.status === 500) {
|
||||
this.errorChannels.push(channel)
|
||||
return []
|
||||
}
|
||||
|
||||
return await this.parseYouTubeRSSFeed(await response.text(), channel.id)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
const errorMessage = this.$t('Invidious API Error (Click to copy)')
|
||||
showToast(`${errorMessage}: ${error}`, 10000, () => {
|
||||
copyToClipboard(error)
|
||||
})
|
||||
switch (failedAttempts) {
|
||||
case 0:
|
||||
return this.getChannelVideosInvidiousScraper(channel, failedAttempts + 1)
|
||||
case 1:
|
||||
if (process.env.IS_ELECTRON && this.backendFallback) {
|
||||
showToast(this.$t('Falling back to the local API'))
|
||||
return this.getChannelVideosLocalRSS(channel, failedAttempts + 1)
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
case 2:
|
||||
return this.getChannelVideosInvidiousScraper(channel, failedAttempts + 1)
|
||||
default:
|
||||
return []
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async parseYouTubeRSSFeed(rssString, channelId) {
|
||||
const xmlDom = new DOMParser().parseFromString(rssString, 'application/xml')
|
||||
|
||||
const channelName = xmlDom.querySelector('author > name').textContent
|
||||
const entries = xmlDom.querySelectorAll('entry')
|
||||
|
||||
const promises = []
|
||||
|
||||
for (const entry of entries) {
|
||||
promises.push(this.parseRSSEntry(entry, channelId, channelName))
|
||||
}
|
||||
|
||||
return await Promise.all(promises)
|
||||
},
|
||||
|
||||
async parseRSSEntry(entry, channelId, channelName) {
|
||||
const published = new Date(entry.querySelector('published').textContent)
|
||||
return {
|
||||
authorId: channelId,
|
||||
author: channelName,
|
||||
// querySelector doesn't support xml namespaces so we have to use getElementsByTagName here
|
||||
videoId: entry.getElementsByTagName('yt:videoId')[0].textContent,
|
||||
title: entry.querySelector('title').textContent,
|
||||
publishedDate: published,
|
||||
publishedText: published.toLocaleString(),
|
||||
viewCount: entry.getElementsByTagName('media:statistics')[0]?.getAttribute('views') || null,
|
||||
type: 'video',
|
||||
lengthSeconds: '0:00',
|
||||
isRSS: true
|
||||
}
|
||||
},
|
||||
|
||||
increaseLimit: function () {
|
||||
this.dataLimit += 100
|
||||
sessionStorage.setItem('subscriptionLimit', this.dataLimit)
|
||||
},
|
||||
|
||||
/**
|
||||
* This function `keyboardShortcutHandler` should always be at the bottom of this file
|
||||
* @param {KeyboardEvent} event the keyboard event
|
||||
* @param {KeyboardEvent} event
|
||||
* @param {string} currentTab
|
||||
*/
|
||||
keyboardShortcutHandler: function (event) {
|
||||
if (event.ctrlKey || document.activeElement.classList.contains('ft-input')) {
|
||||
return
|
||||
focusTab: function (event, currentTab) {
|
||||
if (!event.altKey) {
|
||||
event.preventDefault()
|
||||
|
||||
const visibleTabs = this.visibleTabs
|
||||
|
||||
if (visibleTabs.length === 1) {
|
||||
this.$emit('showOutlines')
|
||||
return
|
||||
}
|
||||
|
||||
let index = visibleTabs.indexOf(currentTab)
|
||||
|
||||
if (event.key === 'ArrowLeft') {
|
||||
index--
|
||||
} else {
|
||||
index++
|
||||
}
|
||||
|
||||
if (index < 0) {
|
||||
index = visibleTabs.length - 1
|
||||
} else if (index > visibleTabs.length - 1) {
|
||||
index = 0
|
||||
}
|
||||
|
||||
this.$refs[visibleTabs[index]].focus()
|
||||
this.$emit('showOutlines')
|
||||
}
|
||||
// Avoid handling events due to user holding a key (not released)
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/repeat
|
||||
if (event.repeat) { return }
|
||||
|
||||
switch (event.key) {
|
||||
case 'r':
|
||||
case 'R':
|
||||
if (!this.isLoading) {
|
||||
this.loadVideosForSubscriptionsFromRemote()
|
||||
}
|
||||
break
|
||||
}
|
||||
},
|
||||
|
||||
...mapActions([
|
||||
'updateShowProgressBar',
|
||||
'updateSubscriptionsCacheForOneChannel',
|
||||
]),
|
||||
|
||||
...mapMutations([
|
||||
'setProgressBarPercentage'
|
||||
])
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,75 +1,80 @@
|
|||
<template>
|
||||
<div>
|
||||
<ft-loader
|
||||
v-if="isLoading"
|
||||
:fullscreen="true"
|
||||
/>
|
||||
<ft-card
|
||||
v-else
|
||||
class="card"
|
||||
>
|
||||
<div
|
||||
v-if="errorChannels.length !== 0"
|
||||
>
|
||||
<h3> {{ $t("Subscriptions.Error Channels") }}</h3>
|
||||
<div>
|
||||
<ft-channel-bubble
|
||||
v-for="(channel, index) in errorChannels"
|
||||
:key="index"
|
||||
:channel-name="channel.name"
|
||||
:channel-id="channel.id"
|
||||
:channel-thumbnail="channel.thumbnail"
|
||||
class="channelBubble"
|
||||
@click="goToChannel(channel.id)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<ft-card class="card">
|
||||
<h3>{{ $t("Subscriptions.Subscriptions") }}</h3>
|
||||
<ft-flex-box
|
||||
v-if="activeVideoList.length === 0"
|
||||
class="subscriptionTabs"
|
||||
role="tablist"
|
||||
:aria-label="$t('Subscriptions.Subscriptions Tabs')"
|
||||
>
|
||||
<p
|
||||
v-if="activeSubscriptionList.length === 0"
|
||||
class="message"
|
||||
<div
|
||||
v-if="!hideSubscriptionsVideos"
|
||||
ref="videos"
|
||||
class="tab"
|
||||
role="tab"
|
||||
:aria-selected="String(currentTab === 'videos')"
|
||||
aria-controls="subscriptionsPanel"
|
||||
:tabindex="currentTab === 'videos' ? 0 : -1"
|
||||
:class="{ selectedTab: currentTab === 'videos' }"
|
||||
@click="changeTab('videos')"
|
||||
@keydown.space.enter.prevent="changeTab('videos')"
|
||||
@keydown.left.right="focusTab($event, 'videos')"
|
||||
>
|
||||
{{ $t("Subscriptions['Your Subscription list is currently empty. Start adding subscriptions to see them here.']") }}
|
||||
</p>
|
||||
<p
|
||||
v-else-if="!fetchSubscriptionsAutomatically && !attemptedFetch"
|
||||
class="message"
|
||||
{{ $t("Global.Videos").toUpperCase() }}
|
||||
</div>
|
||||
<div
|
||||
v-if="!hideSubscriptionsShorts"
|
||||
ref="shorts"
|
||||
class="tab"
|
||||
role="tab"
|
||||
:aria-selected="String(currentTab === 'shorts')"
|
||||
aria-controls="subscriptionsPanel"
|
||||
:tabindex="currentTab === 'shorts' ? 0 : -1"
|
||||
:class="{ selectedTab: currentTab === 'shorts' }"
|
||||
@click="changeTab('shorts')"
|
||||
@keydown.space.enter.prevent="changeTab('shorts')"
|
||||
@keydown.left.right="focusTab($event, 'shorts')"
|
||||
>
|
||||
{{ $t("Subscriptions.Disabled Automatic Fetching") }}
|
||||
</p>
|
||||
<p
|
||||
v-else
|
||||
class="message"
|
||||
{{ $t("Global.Shorts").toUpperCase() }}
|
||||
</div>
|
||||
<div
|
||||
v-if="!hideSubscriptionsLive"
|
||||
ref="live"
|
||||
class="tab"
|
||||
role="tab"
|
||||
:aria-selected="String(currentTab === 'live')"
|
||||
aria-controls="subscriptionsPanel"
|
||||
:tabindex="currentTab === 'live' ? 0 : -1"
|
||||
:class="{ selectedTab: currentTab === 'live' }"
|
||||
@click="changeTab('live')"
|
||||
@keydown.space.enter.prevent="changeTab('live')"
|
||||
@keydown.left.right="focusTab($event, 'live')"
|
||||
>
|
||||
{{ $t("Subscriptions.Empty Channels") }}
|
||||
</p>
|
||||
{{ $t("Global.Live").toUpperCase() }}
|
||||
</div>
|
||||
</ft-flex-box>
|
||||
<ft-element-list
|
||||
v-else
|
||||
:data="activeVideoList"
|
||||
<subscriptions-videos
|
||||
v-if="currentTab === 'videos'"
|
||||
id="subscriptionsPanel"
|
||||
role="tabpanel"
|
||||
/>
|
||||
<ft-flex-box>
|
||||
<ft-button
|
||||
v-if="videoList.length > dataLimit"
|
||||
:label="$t('Subscriptions.Load More Videos')"
|
||||
background-color="var(--primary-color)"
|
||||
text-color="var(--text-with-main-color)"
|
||||
@click="increaseLimit"
|
||||
/>
|
||||
</ft-flex-box>
|
||||
<subscriptions-shorts
|
||||
v-if="currentTab === 'shorts'"
|
||||
id="subscriptionsPanel"
|
||||
role="tabpanel"
|
||||
/>
|
||||
<subscriptions-live
|
||||
v-if="currentTab === 'live'"
|
||||
id="subscriptionsPanel"
|
||||
role="tabpanel"
|
||||
/>
|
||||
<p v-if="currentTab === null">
|
||||
{{ $t("Subscriptions.All Subscription Tabs Hidden", {
|
||||
subsection: $t('Settings.Distraction Free Settings.Sections.Subscriptions Page'),
|
||||
settingsSection: $t('Settings.Distraction Free Settings.Distraction Free Settings')
|
||||
}) }}
|
||||
</p>
|
||||
</ft-card>
|
||||
<ft-icon-button
|
||||
v-if="!isLoading"
|
||||
:icon="['fas', 'sync']"
|
||||
class="floatingTopButton"
|
||||
:title="$t('Subscriptions.Refresh Subscriptions')"
|
||||
:size="12"
|
||||
theme="primary"
|
||||
@click="loadVideosForSubscriptionsFromRemote"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
<ft-element-list
|
||||
v-if="activeData.length > 0 && !isLoading"
|
||||
:data="activeData"
|
||||
:use-channels-hidden-preference="false"
|
||||
/>
|
||||
<ft-flex-box
|
||||
v-if="showLoadMoreButton"
|
||||
|
|
|
@ -361,7 +361,7 @@ export default defineComponent({
|
|||
this.isUpcoming = !!result.basic_info.is_upcoming
|
||||
this.isLiveContent = !!result.basic_info.is_live_content
|
||||
|
||||
const subCount = parseLocalSubscriberCount(result.secondary_info.owner.subscriber_count.text)
|
||||
const subCount = !result.secondary_info.owner.subscriber_count.isEmpty() ? parseLocalSubscriberCount(result.secondary_info.owner.subscriber_count.text) : NaN
|
||||
|
||||
if (!isNaN(subCount)) {
|
||||
this.channelSubscriptionCountText = formatNumber(subCount, subCount >= 10000 ? { notation: 'compact' } : undefined)
|
||||
|
@ -407,6 +407,17 @@ export default defineComponent({
|
|||
this.liveChat = null
|
||||
}
|
||||
|
||||
// region No comment detection
|
||||
// For videos without any comment (comment disabled?)
|
||||
// e.g. https://youtu.be/8NBSwDEf8a8
|
||||
//
|
||||
// `comments_entry_point_header` is null probably when comment disabled
|
||||
// e.g. https://youtu.be/8NBSwDEf8a8
|
||||
// However videos with comments enabled but have no comment
|
||||
// are different (which is not detected here)
|
||||
this.commentsEnabled = result.comments_entry_point_header != null
|
||||
// endregion No comment detection
|
||||
|
||||
// the bypassed result is missing some of the info that we extract in the code above
|
||||
// so we only overwrite the result here
|
||||
// we need the bypassed result for the streaming data and the subtitles
|
||||
|
@ -509,6 +520,7 @@ export default defineComponent({
|
|||
}
|
||||
this.adaptiveFormats = this.videoSourceList
|
||||
|
||||
/** @type {import('../../helpers/api/local').LocalFormat[]} */
|
||||
const formats = [...result.streaming_data.formats, ...result.streaming_data.adaptive_formats]
|
||||
this.downloadLinks = formats.map((format) => {
|
||||
const qualityLabel = format.quality_label ?? format.bitrate
|
||||
|
@ -525,7 +537,7 @@ export default defineComponent({
|
|||
}
|
||||
|
||||
return {
|
||||
url: format.url,
|
||||
url: format.freeTubeUrl,
|
||||
label: label
|
||||
}
|
||||
})
|
||||
|
@ -670,17 +682,6 @@ export default defineComponent({
|
|||
}
|
||||
}
|
||||
|
||||
// region No comment detection
|
||||
// For videos without any comment (comment disabled?)
|
||||
// e.g. https://youtu.be/8NBSwDEf8a8
|
||||
//
|
||||
// `comments_entry_point_header` is null probably when comment disabled
|
||||
// e.g. https://youtu.be/8NBSwDEf8a8
|
||||
// However videos with comments enabled but have no comment
|
||||
// are different (which is not detected here)
|
||||
this.commentsEnabled = result.comments_entry_point_header != null
|
||||
// endregion No comment detection
|
||||
|
||||
this.isLoading = false
|
||||
this.updateTitle()
|
||||
} catch (err) {
|
||||
|
@ -716,7 +717,7 @@ export default defineComponent({
|
|||
|
||||
this.videoTitle = result.title
|
||||
this.videoViewCount = result.viewCount
|
||||
this.channelSubscriptionCountText = result.subCountText || 'FT-0'
|
||||
this.channelSubscriptionCountText = isNaN(result.subCountText) ? '' : result.subCountText
|
||||
if (this.hideVideoLikesAndDislikes) {
|
||||
this.videoLikeCount = null
|
||||
this.videoDislikeCount = null
|
||||
|
@ -946,7 +947,7 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
/**
|
||||
* @param {import('youtubei.js').Misc.Format[]} audioFormats
|
||||
* @param {import('../../helpers/api/local').LocalFormat[]} audioFormats
|
||||
* @returns {AudioSource[]}
|
||||
*/
|
||||
createLocalAudioSourceList: function (audioFormats) {
|
||||
|
@ -973,7 +974,7 @@ export default defineComponent({
|
|||
}
|
||||
|
||||
return {
|
||||
url: format.url,
|
||||
url: format.freeTubeUrl,
|
||||
type: format.mime_type,
|
||||
label: 'Audio',
|
||||
qualityLabel: label
|
||||
|
|
|
@ -113,6 +113,16 @@
|
|||
height: 500px;
|
||||
}
|
||||
|
||||
.watchVideoPlaylist {
|
||||
:deep(.videoThumbnail) {
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.watchVideoRecommendations,
|
||||
.theatreRecommendations {
|
||||
margin: 0 0 16px;
|
||||
|
|
|
@ -58,5 +58,23 @@
|
|||
"playlistShuffle": "--mpv-shuffle",
|
||||
"playlistLoop": "--mpv-loop-playlist"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "SMPlayer",
|
||||
"nameTranslationKey": "Settings.External Player Settings.Players.SMPlayer.Name",
|
||||
"value": "smplayer",
|
||||
"cmdArguments": {
|
||||
"defaultExecutable": "smplayer",
|
||||
"defaultCustomArguments": null,
|
||||
"supportsYtdlProtocol": true,
|
||||
"videoUrl": "",
|
||||
"playlistUrl": "",
|
||||
"startOffset": "-start",
|
||||
"playbackRate": null,
|
||||
"playlistIndex": null,
|
||||
"playlistReverse": null,
|
||||
"playlistShuffle": null,
|
||||
"playlistLoop": null
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -29,6 +29,13 @@ Close: 'إغلاق'
|
|||
Back: 'رجوع'
|
||||
Forward: 'إلى الأمام'
|
||||
|
||||
# Global
|
||||
# Anything shared among components / views should be put here
|
||||
Global:
|
||||
Videos: 'الفيديوهات'
|
||||
Shorts: القصيرة
|
||||
Live: مباشر
|
||||
|
||||
# Search Bar
|
||||
Search / Go to URL: 'ابحث / اذهب إلى رابط'
|
||||
# In Filter Button
|
||||
|
@ -82,6 +89,9 @@ Subscriptions:
|
|||
Disabled Automatic Fetching: لقد قمت بتعطيل الجلب التلقائي للاشتراك. قم بتحديث الاشتراكات
|
||||
لرؤيتها هنا.
|
||||
Empty Channels: لا تحتوي قنواتك التي اشتركت فيها حاليا على أي مقاطع فيديو.
|
||||
All Subscription Tabs Hidden: جميع علامات تبويب الاشتراك مخفية. لمشاهدة المحتوى
|
||||
هنا ، يرجى إظهار بعض علامات التبويب في قسم "{subection}" في "{settingsSection}".
|
||||
Subscriptions Tabs: تبويب الاشتراكات
|
||||
Trending:
|
||||
Trending: 'المحتوى الرائج'
|
||||
Trending Tabs: علامات التبويب الشائعة
|
||||
|
@ -402,6 +412,12 @@ Settings:
|
|||
Channel Page: صفحة القناة
|
||||
Watch Page: صفحة المشاهدة
|
||||
General: عام
|
||||
Subscriptions Page: صفحة الاشتراكات
|
||||
Hide Channel Podcasts: إخفاء بودكاست القناة
|
||||
Hide Channel Releases: إخفاء إصدارات القناة
|
||||
Hide Subscriptions Live: إخفاء الاشتراكات مباشرة
|
||||
Hide Subscriptions Shorts: إخفاء الاشتراكات الفيدوهات القصيرة
|
||||
Hide Subscriptions Videos: إخفاء مقاطع فيديو الاشتراكات
|
||||
The app needs to restart for changes to take effect. Restart and apply change?: البرنامج
|
||||
يحتاج لإعادة التشغيل كي يسري مفعول التغييرات. هل تريد إعادة التشغيل و تطبيق التغييرات؟
|
||||
Proxy Settings:
|
||||
|
@ -621,9 +637,16 @@ Channel:
|
|||
This channel does not currently have any live streams: لا يوجد حاليا أي بث مباشر
|
||||
على هذه القناة
|
||||
Shorts:
|
||||
Shorts: القصيرة
|
||||
This channel does not currently have any shorts: هذه القناة ليس لديها حاليا أي
|
||||
أفلام قصيرة (shorts)
|
||||
Podcasts:
|
||||
Podcasts: البودكاست
|
||||
This channel does not currently have any podcasts: لا تحتوي هذه القناة حاليا على
|
||||
أي بودكاست
|
||||
Releases:
|
||||
Releases: الاصدارات
|
||||
This channel does not currently have any releases: هذه القناة ليس لديها أي إصدارات
|
||||
حاليا
|
||||
Video:
|
||||
Mark As Watched: 'علّمه كفيديو تمت مشاهدته'
|
||||
Remove From History: 'إزالة من سجلّ المشاهدة'
|
||||
|
@ -924,9 +947,11 @@ Tooltips:
|
|||
وتمكين ذاكرة تخزين مؤقت للصور في الذاكرة. سيؤدي إلى زيادة استخدام ذاكرة الوصول
|
||||
العشوائي.
|
||||
Distraction Free Settings:
|
||||
Hide Channels: أدخل اسم القناة أو رقم تعريف القناة لإخفاء كل الفيديوهات وقوائم
|
||||
التشغيل والقناة نفسها من الظهور في "بحث Google" أو "المحتوى الرائج". يجب أن
|
||||
يكون اسم القناة الذي تم إدخاله مطابقا تماما وحساسا لحالة الأحرف.
|
||||
Hide Channels: أدخل اسم قناة أو معرّف القناة لإخفاء جميع مقاطع الفيديو وقوائم
|
||||
التشغيل والقناة نفسها من الظهور في البحث والشهرة والأكثر شعبية والموصى بها.
|
||||
يجب أن يكون اسم القناة الذي تم إدخاله مطابقًا تمامًا وحساسًا لحالة الأحرف.
|
||||
Hide Subscriptions Live: يتم تجاوز هذا الإعداد من خلال إعداد "{appWideSetting}"
|
||||
على مستوى التطبيق، في قسم "{subsection}" من "{settingsSection}"
|
||||
SponsorBlock Settings:
|
||||
UseDeArrowTitles: استبدل عناوين الفيديو بالعناوين التي أرسلها المستخدم من DeArrow.
|
||||
This video is unavailable because of missing formats. This can happen due to country unavailability.: هذا
|
||||
|
|
|
@ -30,6 +30,13 @@ Close: 'Затваряне'
|
|||
Back: 'Назад'
|
||||
Forward: 'Напред'
|
||||
|
||||
# Global
|
||||
# Anything shared among components / views should be put here
|
||||
Global:
|
||||
Videos: 'видеа'
|
||||
Shorts: Кратки видеа
|
||||
Live: На живо
|
||||
|
||||
Version {versionNumber} is now available! Click for more details: 'Версия {versionNumber}
|
||||
е вече налична! Щракнете за повече детайли'
|
||||
Download From Site: 'Сваляне от сайта'
|
||||
|
@ -90,6 +97,10 @@ Subscriptions:
|
|||
Disabled Automatic Fetching: Автоматичното извличане на абонаменти е деактивирано.
|
||||
Обновете абонаментите, за да ги видите тук.
|
||||
Empty Channels: Каналите, за които сте абонирани, в момента нямат никакви видеа.
|
||||
Subscriptions Tabs: Раздели с абонаменти
|
||||
All Subscription Tabs Hidden: Всички раздели на абонамента са скрити. За да виждате
|
||||
съдържанието тук, моля, премахнете скриването на някои раздели в секция "{subsection}"
|
||||
на "{settingsSection}".
|
||||
Trending:
|
||||
Trending: 'Набиращи популярност'
|
||||
Trending Tabs: Раздели за набиращи популярност
|
||||
|
@ -102,11 +113,11 @@ Playlists: 'Плейлисти'
|
|||
User Playlists:
|
||||
Your Playlists: 'Вашите плейлисти'
|
||||
Your saved videos are empty. Click on the save button on the corner of a video to have it listed here: Няма
|
||||
запазени видеоклипове. За запазване щракнете в ъгъла на видеоклипа
|
||||
запазени видеа. За запазване щракнете в ъгъла на видеото
|
||||
Playlist Message: Тази страница не отразява напълно работещи плейлисти. В него са
|
||||
изброени само видеоклипове, които сте запазили или избрали за любими. Когато работата
|
||||
приключи, всички видеоклипове, които в момента са тук, ще бъдат мигрирани в плейлист
|
||||
„Любими“.
|
||||
изброени само видеа, които сте запазили или избрали за предпочитани. Когато работата
|
||||
приключи, всички видеа, които в момента са тук, ще бъдат преместени в плейлист
|
||||
„Предпочитани“.
|
||||
Search bar placeholder: Търсене в плейлиста
|
||||
Empty Search Message: В този плейлист няма видеа, които да отговарят на търсенето
|
||||
ви
|
||||
|
@ -412,6 +423,12 @@ Settings:
|
|||
Side Bar: Странична лента
|
||||
Watch Page: Страница за гледане
|
||||
General: Общи
|
||||
Subscriptions Page: Страница с абонаменти
|
||||
Hide Channel Releases: Скриване на изданията на канала
|
||||
Hide Subscriptions Videos: Скриване на видеата в абонаментите
|
||||
Hide Subscriptions Shorts: Скриване на кратките видеа в абонаментите
|
||||
Hide Subscriptions Live: Скриване на абонаментите на живо
|
||||
Hide Channel Podcasts: Скриване на подкастите на канала
|
||||
The app needs to restart for changes to take effect. Restart and apply change?: Приложението
|
||||
трябва да се рестартира за да се приложат промените. Рестартиране?
|
||||
Proxy Settings:
|
||||
|
@ -445,6 +462,7 @@ Settings:
|
|||
Prompt To Skip: Подкана за пропускане
|
||||
Do Nothing: Не правете нищо
|
||||
Category Color: Категория Цвят
|
||||
UseDeArrowTitles: Използване на DeArrow за заглавия на видео
|
||||
External Player Settings:
|
||||
Custom External Player Arguments: Персонализирани аргументи за външен плейър
|
||||
Custom External Player Executable: Персонализирано изпълнение на външен плейър
|
||||
|
@ -638,7 +656,14 @@ Channel:
|
|||
Shorts:
|
||||
This channel does not currently have any shorts: В момента този канал няма никакви
|
||||
кратки видеа
|
||||
Shorts: Кратки видеа
|
||||
Podcasts:
|
||||
Podcasts: Подкасти
|
||||
This channel does not currently have any podcasts: В момента този канал няма никакви
|
||||
подкасти
|
||||
Releases:
|
||||
Releases: Издания
|
||||
This channel does not currently have any releases: В момента този канал няма никакви
|
||||
издания
|
||||
Video:
|
||||
Mark As Watched: 'Отбелязване като гледано'
|
||||
Remove From History: 'Премахване от историята'
|
||||
|
@ -725,8 +750,8 @@ Video:
|
|||
Open Channel in YouTube: Отваряне на канала в YouTube
|
||||
Started streaming on: Начало на предаването
|
||||
Streamed on: На живо на
|
||||
Video has been removed from your saved list: Видеоклипът е премахнат от вашия запазен
|
||||
списък
|
||||
Video has been removed from your saved list: Видеото е премахнато от вашия списък
|
||||
със запазени
|
||||
Video has been saved: Видеото е запазено
|
||||
Save Video: Запазване на видео
|
||||
translated from English: преведено от английски
|
||||
|
@ -765,7 +790,7 @@ Video:
|
|||
buffered: Буферирани
|
||||
out of: от
|
||||
Resolution: Резолюция
|
||||
Video ID: Идентификатор на видеоклипа
|
||||
Video ID: Идентификатор на видеото
|
||||
Player Dimensions: Размери на плейъра
|
||||
Bitrate: Побитова скорост
|
||||
Bandwidth: Пропускателна способност
|
||||
|
@ -774,7 +799,7 @@ Video:
|
|||
Mimetype: MIME тип
|
||||
Buffered: Буферирани
|
||||
Video statistics are not available for legacy videos: Статистиката не е налична
|
||||
за наследени видеоклипове
|
||||
за наследени видеа
|
||||
Premieres on: Премиера на
|
||||
Premieres in: Премиери в
|
||||
Premieres: Премиерa
|
||||
|
@ -898,7 +923,7 @@ Tooltips:
|
|||
избран Invidious интерфейс. Когато е активиран, локалният интерфейс ще използва
|
||||
старите формати вместо тези на Invidious. Това помага когато видеата, получавани
|
||||
от Invidious не вървят поради регионални ограничения.
|
||||
Scroll Playback Rate Over Video Player: Докато курсорът е върху видеоклипа, натиснете
|
||||
Scroll Playback Rate Over Video Player: Докато курсорът е върху видеото, натиснете
|
||||
и задръжте клавиша Control (Command Key за Mac) и превъртете колелцето на мишката
|
||||
напред или назад, за да контролирате скоростта на възпроизвеждане. Натиснете
|
||||
и задръжте клавиша Control (Command Key за Mac) и щракнете с левия бутон на
|
||||
|
@ -948,9 +973,14 @@ Tooltips:
|
|||
на RAM паметта.
|
||||
Distraction Free Settings:
|
||||
Hide Channels: Въведете име или идентификатор на канал, за да скриете всички видеа,
|
||||
плейлисти и самия канал от показване в търсенето или набиращите популярност.
|
||||
Въведеното име трябва да съвпада напълно и е чувствително към главни и малки
|
||||
букви.
|
||||
плейлисти и самия канал от показване в търсенето, тенденциите, най-популярните
|
||||
и препоръчаните. Въведеното име трябва да съвпада напълно и е чувствително към
|
||||
главни и малки букви.
|
||||
Hide Subscriptions Live: Тази настройка се отменя от настройката за цялото приложение
|
||||
"{appWideSetting}" в секция "{subsection}" на "{settingsSection}"
|
||||
SponsorBlock Settings:
|
||||
UseDeArrowTitles: Заменя заглавията на видеата с подадени от потребителите заглавия
|
||||
от DeArrow.
|
||||
More: Още
|
||||
Playing Next Video Interval: Пускане на следващото видео веднага. Щракнете за отказ.
|
||||
| Пускане на следващото видео след {nextVideoInterval} секунда. Щракнете за отказ.
|
||||
|
|
|
@ -30,6 +30,11 @@ Close: 'Tancar'
|
|||
Back: 'Enrere'
|
||||
Forward: 'Endavant'
|
||||
|
||||
# Global
|
||||
# Anything shared among components / views should be put here
|
||||
Global:
|
||||
Videos: 'Vídeos'
|
||||
|
||||
Version {versionNumber} is now available! Click for more details: 'La versió {versionNumber}
|
||||
està disponible! Fes clic per a més detalls'
|
||||
Download From Site: 'Descarrega des del web'
|
||||
|
|
|
@ -29,6 +29,13 @@ Close: 'Zavřít'
|
|||
Back: 'Zpět'
|
||||
Forward: 'Dopředu'
|
||||
|
||||
# Global
|
||||
# Anything shared among components / views should be put here
|
||||
Global:
|
||||
Videos: 'Videa'
|
||||
Shorts: Shorts
|
||||
Live: Živě
|
||||
|
||||
Version {versionNumber} is now available! Click for more details: 'Verze {versionNumber}
|
||||
je k dispozici! Klikněte pro více informací'
|
||||
Download From Site: 'Stáhnout ze stránky'
|
||||
|
@ -89,6 +96,10 @@ Subscriptions:
|
|||
Empty Channels: Vaše odebírané kanály momentálně nemají žádná videa.
|
||||
Disabled Automatic Fetching: Máte zakázané automatické získávání odběrů. Obnovte
|
||||
odběry, abyste je zde mohli vidět.
|
||||
Subscriptions Tabs: Karty s odběry
|
||||
All Subscription Tabs Hidden: Všechny karty předplatného jsou skryté. Chcete-li
|
||||
zde zobrazit obsah, zrušte prosím skrytí některých záložek v sekci „{subsection}“
|
||||
v „{settingsSection}“.
|
||||
Trending:
|
||||
Trending: 'Trendy'
|
||||
Trending Tabs: Tabulka trendů
|
||||
|
@ -334,6 +345,12 @@ Settings:
|
|||
Channel Page: Stránka kanálu
|
||||
Watch Page: Stránka sledování
|
||||
General: Obecné
|
||||
Subscriptions Page: Stránka s odběry
|
||||
Hide Channel Podcasts: Skrýt podcasty kanálu
|
||||
Hide Channel Releases: Skrýt vydání kanálu
|
||||
Hide Subscriptions Shorts: Skrýt Shorts odběrů
|
||||
Hide Subscriptions Videos: Skrýt videa odběrů
|
||||
Hide Subscriptions Live: Skrýt živá vysílání odběrů
|
||||
Data Settings:
|
||||
Data Settings: 'Nastavení dat'
|
||||
Select Import Type: 'Vybrat typ importu'
|
||||
|
@ -436,6 +453,7 @@ Settings:
|
|||
Prompt To Skip: Zeptat se na přeskočení
|
||||
Show In Seek Bar: Zobrazit v liště
|
||||
Category Color: Barva kategorie
|
||||
UseDeArrowTitles: Použít názvy videí z DeArrow
|
||||
External Player Settings:
|
||||
Custom External Player Arguments: Argumenty vlastního externího přehrávače
|
||||
Custom External Player Executable: Spustitelný vlastní externí přehrávač
|
||||
|
@ -622,7 +640,14 @@ Channel:
|
|||
době nemá žádné živé přenosy
|
||||
Shorts:
|
||||
This channel does not currently have any shorts: Tento kanál nemá žádné shorts
|
||||
Shorts: Shorts
|
||||
Releases:
|
||||
Releases: Vydání
|
||||
This channel does not currently have any releases: Tento kanál momentálně nemá
|
||||
žádná vydání
|
||||
Podcasts:
|
||||
This channel does not currently have any podcasts: Tento kanál momentálně nemá
|
||||
žádné podcasty
|
||||
Podcasts: Podcasty
|
||||
Video:
|
||||
Mark As Watched: 'Označit jako zhlédnuto'
|
||||
Remove From History: 'Odstranit z historie'
|
||||
|
@ -905,8 +930,13 @@ Tooltips:
|
|||
mezipaměť pro obrázky v paměti. Povede to ke zvýšenému využití RAM.
|
||||
Distraction Free Settings:
|
||||
Hide Channels: Zadejte název nebo ID kanálu pro skrytí všech videí, playlistů
|
||||
a samotného kanálu před zobrazením ve vyhledávání nebo trendech. Zadaný název
|
||||
kanálu se musí zcela shodovat a rozlišují se v něm velká a malá písmena.
|
||||
a samotného kanálu před zobrazením ve vyhledávání, trendech, nejpopulárnějších
|
||||
a doporučených. Zadaný název kanálu se musí zcela shodovat a rozlišují se v
|
||||
něm velká a malá písmena.
|
||||
Hide Subscriptions Live: Toto nastavení je nadřazeno nastavením celé aplikace
|
||||
„{appWideSetting}“ v části „{subsection}“ v části „{settingsSection}“
|
||||
SponsorBlock Settings:
|
||||
UseDeArrowTitles: Nahradit názvy videí vlastními názvy od uživatelů DeArrow.
|
||||
Local API Error (Click to copy): 'Chyba lokálního API (kliknutím zkopírujete)'
|
||||
Invidious API Error (Click to copy): 'Chyba Invidious API (kliknutím zkopírujete)'
|
||||
Falling back to Invidious API: 'Přepínám na Invidious API'
|
||||
|
|
|
@ -29,6 +29,11 @@ Close: 'Luk'
|
|||
Back: 'Tilbage'
|
||||
Forward: 'Fremad'
|
||||
|
||||
# Global
|
||||
# Anything shared among components / views should be put here
|
||||
Global:
|
||||
Videos: Videoer
|
||||
|
||||
Version {versionNumber} is now available! Click for more details: 'Version {versionNumber}
|
||||
er nu tilgængelig! Klik for flere detaljer'
|
||||
Download From Site: 'Hent Fra Netsted'
|
||||
|
@ -60,12 +65,14 @@ Search Filters:
|
|||
Videos: 'Videoer'
|
||||
Channels: 'Kanaler'
|
||||
#& Playlists
|
||||
Movies: Film
|
||||
Duration:
|
||||
Duration: 'Længde'
|
||||
All Durations: 'Alle Længder'
|
||||
Short (< 4 minutes): 'Kort (< 4 minutter)'
|
||||
Long (> 20 minutes): 'Lang (> 20 minutter)'
|
||||
# On Search Page
|
||||
Medium (4 - 20 minutes): Medium (4-20 minutter)
|
||||
Search Results: 'Søgeresultater'
|
||||
Fetching results. Please wait: 'Skaffer resultater. Vent venligst'
|
||||
Fetch more results: 'Skaf flere resultater'
|
||||
|
@ -83,8 +90,11 @@ Subscriptions:
|
|||
Refresh Subscriptions: 'Genopfrisk Abonnementer'
|
||||
Load More Videos: Indlæs Flere Videoer
|
||||
This profile has a large number of subscriptions. Forcing RSS to avoid rate limiting: Denne
|
||||
profil har et stort antal abonnementer. Tvinger RSS for at undgå adgangsbegrænsning
|
||||
profil har et stort antal abonnementer. Gennemtvinger RSS for at undgå adgangsbegrænsning
|
||||
Error Channels: Kanaler med Fejl
|
||||
Empty Channels: De kanaler, du abonnerer på, har i øjeblikket ingen videoer.
|
||||
Disabled Automatic Fetching: Du har deaktiveret automatisk hentning af abonnementer.
|
||||
Opdater abonnementer for at se dem her.
|
||||
Trending:
|
||||
Trending: 'Trender'
|
||||
Music: Musik
|
||||
|
@ -263,6 +273,7 @@ Settings:
|
|||
Video Playback Rate Interval: Videoafspilningshastighed Interval
|
||||
Scroll Playback Rate Over Video Player: Scroll-afspilningshastighed i Videoafspiller
|
||||
Scroll Volume Over Video Player: Scroll-lydstyrke i Videoafspiller
|
||||
Skip by Scrolling Over Video Player: Spring over ved at scrolle over videoafspilleren
|
||||
Privacy Settings:
|
||||
Privacy Settings: 'Privatlivsindstillinger'
|
||||
Remember History: 'Husk Historik'
|
||||
|
@ -592,8 +603,8 @@ Video:
|
|||
i øjeblikket ikke i dette build.'
|
||||
'Chat is disabled or the Live Stream has ended.': 'Chat er deaktiveret, eller Livestream
|
||||
er slut.'
|
||||
Live chat is enabled. Chat messages will appear here once sent.: 'Direkte chat
|
||||
er aktiveret. Chatbeskeder vil fremgå her, når de er sendt.'
|
||||
Live chat is enabled. Chat messages will appear here once sent.: 'Direkte chat er
|
||||
aktiveret. Chatbeskeder vil fremgå her, når de er sendt.'
|
||||
'Live Chat is currently not supported with the Invidious API. A direct connection to YouTube is required.': 'Direkte
|
||||
Chat understøttes i øjeblikket ikke med Invidious-APIen. Direkte forbindelse
|
||||
til YouTube kræves.'
|
||||
|
@ -815,9 +826,9 @@ Tooltips:
|
|||
Fallback to Non-Preferred Backend on Failure: Når din foretrukne API oplever et
|
||||
problem, vil FreeTube automatisk forsøge at bruge din ikke-foretrukne API som
|
||||
reservemetode, når det er aktiveret.
|
||||
External Link Handling: "Vælg standardopførslen for når du klikker på et link,\
|
||||
\ der ikke kan blive åbnet i FreeTube.\nSom standard åbner FreeTube det link,\
|
||||
\ du klikker på, i din standardbrowser.\n"
|
||||
External Link Handling: "Vælg standardopførslen for når du klikker på et link,
|
||||
der ikke kan blive åbnet i FreeTube.\nSom standard åbner FreeTube det link,
|
||||
du klikker på, i din standardbrowser.\n"
|
||||
External Player Settings:
|
||||
Custom External Player Executable: Som standard antager FreeTube at den valgte
|
||||
eksterne afspiller kan findes via miljøvariablet PATH. En brugerdefineret sti
|
||||
|
@ -871,3 +882,4 @@ Screenshot Success: Gemte skærmbillede som "{filePath}"
|
|||
Playing Next Video Interval: Afspiller næste video om lidt. Klik for at afbryde. |
|
||||
Afspiller næste video om {nextVideoInterval} sekund. Klik for at afbryde. | Afspiller
|
||||
næste video om {nextVideoInterval} sekunder. Klik for at afbryde.
|
||||
Preferences: Præferencer
|
||||
|
|
|
@ -28,6 +28,13 @@ Close: Schließen
|
|||
Back: Zurück
|
||||
Forward: Vorwärts
|
||||
|
||||
# Global
|
||||
# Anything shared among components / views should be put here
|
||||
Global:
|
||||
Videos: Videos
|
||||
Shorts: Shorts
|
||||
Live: Live
|
||||
|
||||
# Search Bar
|
||||
Search / Go to URL: Suche / Geh zu URL
|
||||
# In Filter Button
|
||||
|
@ -85,6 +92,10 @@ Subscriptions:
|
|||
Disabled Automatic Fetching: Du hast den automatischen Abruf von Abonnements deaktiviert.
|
||||
Aktualisiere die Abonnements, um sie hier zu sehen.
|
||||
Empty Channels: Deine abonnierten Kanäle haben derzeit keine Videos.
|
||||
Subscriptions Tabs: Registerkarten für Abonnements
|
||||
All Subscription Tabs Hidden: Alle Registerkarten für Abonnements sind ausgeblendet.
|
||||
Um den Inhalt hier zu sehen, blenden Sie bitte einige Registerkarten im Abschnitt
|
||||
„{subsection}“ in „{settingsSection}“ ein.
|
||||
Trending:
|
||||
Trending: Trends
|
||||
Trending Tabs: Trendtabs
|
||||
|
@ -416,6 +427,12 @@ Settings:
|
|||
Channel Page: Kanalseite
|
||||
General: Allgemein
|
||||
Watch Page: Seite beobachten
|
||||
Subscriptions Page: Abonnements-Seite
|
||||
Hide Channel Podcasts: Kanal-Podcasts ausblenden
|
||||
Hide Subscriptions Videos: Videos der Abonnements ausblenden
|
||||
Hide Subscriptions Shorts: Shorts der Abonnements ausblenden
|
||||
Hide Channel Releases: Kanalveröffentlichungen ausblenden
|
||||
Hide Subscriptions Live: Live der Abonnements ausblenden
|
||||
The app needs to restart for changes to take effect. Restart and apply change?: Um
|
||||
die Änderungen anzuwenden muss die Anwendung neustarten. Jetzt neustarten und
|
||||
Änderungen aktivieren?
|
||||
|
@ -449,6 +466,7 @@ Settings:
|
|||
Prompt To Skip: Aufforderung zum Überspringen
|
||||
Do Nothing: Nichts tun
|
||||
Category Color: Kategoriefarbe
|
||||
UseDeArrowTitles: DeArrow-Video-Titel verwenden
|
||||
External Player Settings:
|
||||
Ignore Unsupported Action Warnings: Nicht unterstützte Aktionswarnungen ignorieren
|
||||
External Player: Externer Player
|
||||
|
@ -609,9 +627,16 @@ Channel:
|
|||
This channel does not currently have any live streams: Dieser Kanal hat derzeit
|
||||
keine Live-Streams
|
||||
Shorts:
|
||||
Shorts: Shorts
|
||||
This channel does not currently have any shorts: Dieser Kanal hat derzeit keine
|
||||
Shorts
|
||||
Podcasts:
|
||||
Podcasts: Podcasts
|
||||
This channel does not currently have any podcasts: Dieser Kanal hat aktuell keine
|
||||
Podcasts
|
||||
Releases:
|
||||
Releases: Veröffentlichungen
|
||||
This channel does not currently have any releases: Dieser Kanel hat aktuell keine
|
||||
Veröffentlichungen
|
||||
Video:
|
||||
Open in YouTube: In YouTube öffnen
|
||||
Copy YouTube Link: YouTube-Link kopieren
|
||||
|
@ -974,10 +999,17 @@ Tooltips:
|
|||
und aktiviert einen benutzerdefinierten In-Memory-Image-Cache. Dies führt zu
|
||||
einer erhöhten Nutzung des Direktzugriffsspeichers.
|
||||
Distraction Free Settings:
|
||||
Hide Channels: Geben Sie einen Kanalnamen oder eine Kanal-ID ein, um alle Videos,
|
||||
Wiedergabelisten und den Kanal selbst vor der Anzeige in der Suche oder den
|
||||
Trends zu verbergen. Der eingegebene Kanalname muss vollständig übereinstimmen
|
||||
und es wird zwischen Groß- und Kleinschreibung unterschieden.
|
||||
Hide Channels: Geben Sie einen Kanalnamen oder eine Kanal-ID ein, um zu verhindern,
|
||||
dass alle Videos, Wiedergabelisten und der Kanal selbst in der Suche, den Trends,
|
||||
den beliebtesten und den empfohlenen Videos angezeigt werden. Der eingegebene
|
||||
Kanalname muss vollständig übereinstimmen und es wird zwischen Groß- und Kleinschreibung
|
||||
unterschieden.
|
||||
Hide Subscriptions Live: Diese Einstellung wird durch die app-weite Einstellung
|
||||
„{appWideSetting}“ im Abschnitt „{subsection}“ des Abschnitts „{settingsSection}“
|
||||
außer Kraft gesetzt
|
||||
SponsorBlock Settings:
|
||||
UseDeArrowTitles: Ersetzen Sie Videotitel durch von Benutzern eingereichte Titel
|
||||
von DeArrow.
|
||||
Playing Next Video Interval: Nächstes Video wird sofort abgespielt. Zum Abbrechen
|
||||
klicken. | Nächstes Video wird in {nextVideoInterval} Sekunden abgespielt. Zum Abbrechen
|
||||
klicken. | Nächstes Video wird in {nextVideoInterval} Sekunden abgespielt. Zum Abbrechen
|
||||
|
|
|
@ -30,6 +30,13 @@ Close: 'Κλείσιμο'
|
|||
Back: 'Μετάβαση πίσω'
|
||||
Forward: 'Μετάβαση μπροστά'
|
||||
|
||||
# Global
|
||||
# Anything shared among components / views should be put here
|
||||
Global:
|
||||
Videos: 'Βίντεο'
|
||||
Shorts: Shorts
|
||||
Live: Ζωντανά
|
||||
|
||||
# Search Bar
|
||||
Search / Go to URL: 'Αναζήτηση/Μετάβαση στη URL'
|
||||
# In Filter Button
|
||||
|
@ -86,6 +93,10 @@ Subscriptions:
|
|||
Disabled Automatic Fetching: Έχετε απενεργοποιήσει την αυτόματη ανάκτηση συνδρομής.
|
||||
Ανανεώστε τις συνδρομές για να τις δείτε εδώ.
|
||||
Empty Channels: Τα εγγεγραμμένα κανάλια σας προς το παρόν δεν έχουν βίντεο.
|
||||
Subscriptions Tabs: Καρτέλες Συνδρομών
|
||||
All Subscription Tabs Hidden: Όλες οι καρτέλες συνδρομής είναι κρυφές. Για να δείτε
|
||||
περιεχόμενο εδώ, αποκρύψτε ορισμένες καρτέλες στην ενότητα "{subsection}" στο
|
||||
"{settingsSection}".
|
||||
Trending:
|
||||
Trending: 'Τάσεις'
|
||||
Gaming: Παιχνίδια
|
||||
|
@ -413,10 +424,16 @@ Settings:
|
|||
Channel Page: Σελίδα Καναλιού
|
||||
Watch Page: Σελίδα Παρακολούθησης
|
||||
General: Γενικά
|
||||
Subscriptions Page: Σελίδα Συνδρομών
|
||||
Hide Featured Channels: Απόκρυψη Προτεινόμενων Καναλιών
|
||||
Hide Channel Playlists: Απόκρυψη Λιστών Αναπαραγωγής Καναλιών
|
||||
Hide Channel Community: Απόκρυψη Κοινότητας Καναλιού
|
||||
Hide Channel Shorts: Απόκρυψη Shorts Καναλιού
|
||||
Hide Channel Releases: Απόκρυψη Kυκλοφοριών Kαναλιού
|
||||
Hide Channel Podcasts: Απόκρυψη Podcasts Καναλιού
|
||||
Hide Subscriptions Videos: Απόκρυψη Βίντεο Συνδρομών
|
||||
Hide Subscriptions Shorts: Απόκρυψη Shorts Συνδρομών
|
||||
Hide Subscriptions Live: Απόκρυψη Live Συνδρομών
|
||||
The app needs to restart for changes to take effect. Restart and apply change?: Η
|
||||
εφαρμογή πρέπει να κάνει επανεκκίνηση για να εφαρμοστούν οι αλλαγές. Επανεκκίνηση
|
||||
και εφαρμογή αλλαγών;
|
||||
|
@ -461,6 +478,7 @@ Settings:
|
|||
Show In Seek Bar: Εμφάνιση Στη Γραμμή Αναζήτησης
|
||||
Prompt To Skip: Προτροπή Για Παράλειψη
|
||||
Category Color: Χρώμα Κατηγορίας
|
||||
UseDeArrowTitles: Χρήση Τίτλων Βίντεο DeArrow
|
||||
Download Settings:
|
||||
Download Settings: Λήψη ρυθμίσεων
|
||||
Ask Download Path: Ζητήστε τη διαδρομή λήψης
|
||||
|
@ -652,9 +670,16 @@ Channel:
|
|||
This channel does not currently have any live streams: Αυτό το κανάλι δεν έχει
|
||||
προς το παρόν ζωντανές ροές
|
||||
Shorts:
|
||||
Shorts: Shorts
|
||||
This channel does not currently have any shorts: Αυτό το κανάλι δεν έχει προς
|
||||
το παρόν κανένα shorts
|
||||
Releases:
|
||||
Releases: Κυκλοφορίες
|
||||
This channel does not currently have any releases: Αυτό το κανάλι δεν έχει προς
|
||||
το παρόν κυκλοφορίες
|
||||
Podcasts:
|
||||
Podcasts: Podcasts
|
||||
This channel does not currently have any podcasts: Αυτό το κανάλι δεν έχει αυτήν
|
||||
τη στιγμή podcasts
|
||||
Video:
|
||||
Mark As Watched: 'Επισήμανση ως παρακολουθημένο'
|
||||
Remove From History: 'Κατάργηση από το ιστορικό'
|
||||
|
@ -986,8 +1011,14 @@ Tooltips:
|
|||
Distraction Free Settings:
|
||||
Hide Channels: Εισαγάγετε ένα όνομα καναλιού ή ένα αναγνωριστικό καναλιού για
|
||||
να αποκρύψετε όλα τα βίντεο, τις λίστες αναπαραγωγής και το ίδιο το κανάλι ώστε
|
||||
να μην εμφανίζονται στην αναζήτηση ή στις τάσεις. Το όνομα του καναλιού που
|
||||
καταχωρίσατε πρέπει να ταιριάζει απόλυτα και να κάνει διάκριση πεζών-κεφαλαίων.
|
||||
να μην εμφανίζονται στην αναζήτηση, στις τάσεις, στα πιο δημοφιλή και προτεινόμενα.
|
||||
Το όνομα του καναλιού που καταχωρίσατε πρέπει να ταιριάζει απόλυτα και να κάνει
|
||||
διάκριση πεζών-κεφαλαίων.
|
||||
Hide Subscriptions Live: Αυτή η ρύθμιση παρακάμπτεται από τη ρύθμιση "{appWideSetting}"
|
||||
σε όλη την εφαρμογή, στην ενότητα "{subsection}" του "{settingsSection}"
|
||||
SponsorBlock Settings:
|
||||
UseDeArrowTitles: Αντικαταστήστε τους τίτλους βίντεο με τίτλους που υποβλήθηκαν
|
||||
από τους χρήστες από το DeArrow.
|
||||
Playing Next Video Interval: Αναπαραγωγή επόμενου βίντεο άμεσα. Κάντε κλικ για ακύρωση.
|
||||
| Αναπαραγωγή επόμενου βίντεο σε {nextVideoInterval} δευτερόλεπτο. Κάντε κλικ για
|
||||
ακύρωση. | Αναπαραγωγή επόμενου βίντεο σε {nextVideoInterval} δευτερόλεπτα. Κάντε
|
||||
|
|
|
@ -39,6 +39,13 @@ A new blog is now available, {blogTitle}. Click to view more: A new blog is now
|
|||
Click to view more
|
||||
Are you sure you want to open this link?: Are you sure you want to open this link?
|
||||
|
||||
# Global
|
||||
# Anything shared among components / views should be put here
|
||||
Global:
|
||||
Videos: Videos
|
||||
Shorts: Shorts
|
||||
Live: Live
|
||||
|
||||
# Search Bar
|
||||
Search / Go to URL: Search / Go to URL
|
||||
Search Bar:
|
||||
|
@ -93,6 +100,8 @@ Subscriptions:
|
|||
'Getting Subscriptions. Please wait.': Getting Subscriptions. Please wait.
|
||||
Refresh Subscriptions: Refresh Subscriptions
|
||||
Load More Videos: Load More Videos
|
||||
Subscriptions Tabs: Subscriptions Tabs
|
||||
All Subscription Tabs Hidden: 'All subscription tabs are hidden. To see content here, please unhide some tabs in the "{subsection}" section in "{settingsSection}".'
|
||||
More: More
|
||||
Channels:
|
||||
Channels: Channels
|
||||
|
@ -323,6 +332,7 @@ Settings:
|
|||
Distraction Free Settings: Distraction Free Settings
|
||||
Sections:
|
||||
Side Bar: Side Bar
|
||||
Subscriptions Page: Subscriptions Page
|
||||
Channel Page: Channel Page
|
||||
Watch Page: Watch Page
|
||||
General: General
|
||||
|
@ -349,6 +359,11 @@ Settings:
|
|||
Hide Channel Playlists: Hide Channel Playlists
|
||||
Hide Channel Community: Hide Channel Community
|
||||
Hide Channel Shorts: Hide Channel Shorts
|
||||
Hide Channel Podcasts: Hide Channel Podcasts
|
||||
Hide Channel Releases: Hide Channel Releases
|
||||
Hide Subscriptions Videos: Hide Subscriptions Videos
|
||||
Hide Subscriptions Shorts: Hide Subscriptions Shorts
|
||||
Hide Subscriptions Live: Hide Subscriptions Live
|
||||
Data Settings:
|
||||
Data Settings: Data Settings
|
||||
Select Import Type: Select Import Type
|
||||
|
@ -545,7 +560,6 @@ Channel:
|
|||
Oldest: Oldest
|
||||
Most Popular: Most Popular
|
||||
Shorts:
|
||||
Shorts: Shorts
|
||||
This channel does not currently have any shorts: This channel does not currently have any shorts
|
||||
Live:
|
||||
Live: Live
|
||||
|
@ -559,6 +573,12 @@ Channel:
|
|||
Last Video Added: Last Video Added
|
||||
Newest: Newest
|
||||
Oldest: Oldest
|
||||
Podcasts:
|
||||
Podcasts: Podcasts
|
||||
This channel does not currently have any podcasts: This channel does not currently have any podcasts
|
||||
Releases:
|
||||
Releases: Releases
|
||||
This channel does not currently have any releases: This channel does not currently have any releases
|
||||
About:
|
||||
About: About
|
||||
Channel Description: Channel Description
|
||||
|
@ -836,8 +856,9 @@ Tooltips:
|
|||
you want to be passed to the external player.
|
||||
DefaultCustomArgumentsTemplate: "(Default: '{defaultCustomArguments}')"
|
||||
Distraction Free Settings:
|
||||
Hide Channels: Enter a channel name or channel ID to hide all videos, playlists and the channel itself from appearing in search or trending.
|
||||
Hide Channels: Enter a channel name or channel ID to hide all videos, playlists and the channel itself from appearing in search, trending, most popular and recommended.
|
||||
The channel name entered must be a complete match and is case sensitive.
|
||||
Hide Subscriptions Live: 'This setting is overridden by the app-wide "{appWideSetting}" setting, in the "{subsection}" section of the "{settingsSection}"'
|
||||
Subscription Settings:
|
||||
Fetch Feeds from RSS: When enabled, FreeTube will use RSS instead of its default
|
||||
method for grabbing your subscription feed. RSS is faster and prevents IP blocking,
|
||||
|
|
|
@ -29,6 +29,13 @@ Close: 'Close'
|
|||
Back: 'Back'
|
||||
Forward: 'Forward'
|
||||
|
||||
# Global
|
||||
# Anything shared among components / views should be put here
|
||||
Global:
|
||||
Videos: Videos
|
||||
Shorts: Shorts
|
||||
Live: Live
|
||||
|
||||
Version {versionNumber} is now available! Click for more details: 'Version {versionNumber}
|
||||
is now available! Click for more details'
|
||||
Download From Site: 'Download From Site'
|
||||
|
@ -88,6 +95,9 @@ Subscriptions:
|
|||
Disabled Automatic Fetching: You have disabled automatic subscription fetching.
|
||||
Refresh subscriptions to see them here.
|
||||
Empty Channels: Your subscribed channels currently does not have any videos.
|
||||
Subscriptions Tabs: Subscriptions tabs
|
||||
All Subscription Tabs Hidden: All subscription tabs are hidden. To see content here,
|
||||
please unhide some tabs in the ‘{subsection}’ section in ‘{settingsSection}’.
|
||||
Trending:
|
||||
Trending: 'Trending'
|
||||
Trending Tabs: Trending Tabs
|
||||
|
@ -412,10 +422,16 @@ Settings:
|
|||
Hide Channel Community: Hide channel community
|
||||
Hide Channel Shorts: Hide channel shorts
|
||||
Sections:
|
||||
Side Bar: Side Bar
|
||||
Side Bar: Side bar
|
||||
General: General
|
||||
Channel Page: Channel Page
|
||||
Watch Page: Watch Page
|
||||
Subscriptions Page: Subscriptions page
|
||||
Hide Channel Podcasts: Hide channel podcasts
|
||||
Hide Channel Releases: Hide channel releases
|
||||
Hide Subscriptions Shorts: Hide subscriptions shorts
|
||||
Hide Subscriptions Live: Hide subscriptions live
|
||||
Hide Subscriptions Videos: Hide subscriptions videos
|
||||
The app needs to restart for changes to take effect. Restart and apply change?: The
|
||||
app needs to restart for changes to take effect. Do you want to restart and apply
|
||||
the changes?
|
||||
|
@ -448,6 +464,7 @@ Settings:
|
|||
Prompt To Skip: Prompt to skip
|
||||
Do Nothing: Do nothing
|
||||
Category Color: Category colour
|
||||
UseDeArrowTitles: Use DeArrow video titles
|
||||
Download Settings:
|
||||
Download Settings: Download Settings
|
||||
Ask Download Path: Ask for download path
|
||||
|
@ -611,9 +628,16 @@ Channel:
|
|||
have any live streams
|
||||
Live: Live
|
||||
Shorts:
|
||||
Shorts: Shorts
|
||||
This channel does not currently have any shorts: This channel does not currently
|
||||
have any shorts
|
||||
Podcasts:
|
||||
Podcasts: Podcasts
|
||||
This channel does not currently have any podcasts: This channel does not currently
|
||||
have any podcasts
|
||||
Releases:
|
||||
Releases: Releases
|
||||
This channel does not currently have any releases: This channel does not currently
|
||||
have any releases
|
||||
Video:
|
||||
Mark As Watched: 'Mark As Watched'
|
||||
Remove From History: 'Remove From History'
|
||||
|
@ -918,8 +942,12 @@ Tooltips:
|
|||
in-memory image cache. Will lead to increased RAM usage.
|
||||
Distraction Free Settings:
|
||||
Hide Channels: Enter a channel name or channel ID to hide all videos, playlists
|
||||
and the channel itself from appearing in search or trending. The channel name
|
||||
entered must be a complete match and is case sensitive.
|
||||
and the channel itself from appearing in search, trending, most popular and
|
||||
recommended. The channel name entered must be a complete match and is case sensitive.
|
||||
Hide Subscriptions Live: This setting is overridden by the app-wide ‘{appWideSetting}’
|
||||
setting, in the ‘{subsection}’ section of the ‘{settingsSection}’
|
||||
SponsorBlock Settings:
|
||||
UseDeArrowTitles: Replace video titles with user-submitted titles from DeArrow.
|
||||
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.
|
||||
|
|
|
@ -27,6 +27,11 @@ Close: 'Cerrar'
|
|||
Back: 'Volver'
|
||||
Forward: 'Adelante'
|
||||
|
||||
# Global
|
||||
# Anything shared among components / views should be put here
|
||||
Global:
|
||||
Videos: 'Videos'
|
||||
|
||||
# Search Bar
|
||||
Search / Go to URL: 'Buscar / Ir a la URL'
|
||||
# In Filter Button
|
||||
|
|
|
@ -30,6 +30,13 @@ Close: 'Cerrar'
|
|||
Back: 'Atrás'
|
||||
Forward: 'Adelante'
|
||||
|
||||
# Global
|
||||
# Anything shared among components / views should be put here
|
||||
Global:
|
||||
Videos: 'Vídeos'
|
||||
Shorts: Cortos
|
||||
Live: En directo
|
||||
|
||||
# Search Bar
|
||||
Search / Go to URL: 'Buscar / Ir a la dirección'
|
||||
# In Filter Button
|
||||
|
@ -84,6 +91,10 @@ Subscriptions:
|
|||
Actualice las suscripciones para verlas aquí.
|
||||
Empty Channels: Los canales a los que está suscrito no tienen actualmente ningún
|
||||
vídeo.
|
||||
Subscriptions Tabs: Pestañas de suscripciones
|
||||
All Subscription Tabs Hidden: Todas las pestañas de las suscripciones están ocultas.
|
||||
Para ver el contenido, por favor, desoculta algunas pestañas en la sección «{subsection}»
|
||||
en «{settingsSection}».
|
||||
Trending:
|
||||
Trending: 'Tendencias'
|
||||
Default: Predeterminado
|
||||
|
@ -114,7 +125,7 @@ Settings:
|
|||
# On Settings Page
|
||||
Settings: 'Ajustes'
|
||||
General Settings:
|
||||
General Settings: 'General'
|
||||
General Settings: 'Ajustes generales'
|
||||
Fallback to Non-Preferred Backend on Failure: 'Usar motor API secundario en caso
|
||||
de fallo'
|
||||
Enable Search Suggestions: 'Activar sugerencias de búsqueda'
|
||||
|
@ -267,8 +278,8 @@ Settings:
|
|||
File Name Tooltip: Puede utilizar las siguientes variables. %Y Año 4 dígitos.
|
||||
%M Mes 2 dígitos. %D Día 2 dígitos. %H Hora 2 dígitos. %N Minuto 2 dígitos.
|
||||
%S Segundo 2 dígitos. %T Milisegundo 3 dígitos. %s Video Segundo. %t Video
|
||||
Milisegundo 3 dígitos. %i Video ID. También puede utilizar "\" o "/" para
|
||||
crear subcarpetas.
|
||||
Milisegundo 3 dígitos. %i Video ID. También puede utilizar \ o / para crear
|
||||
subcarpetas.
|
||||
Enter Fullscreen on Display Rotate: Cambiar a pantalla completa al girar la pantalla
|
||||
Skip by Scrolling Over Video Player: Omitir al desplazarse sobre el reproductor
|
||||
de vídeo
|
||||
|
@ -408,6 +419,12 @@ Settings:
|
|||
Channel Page: Página del canal
|
||||
Watch Page: Ver la página
|
||||
General: General
|
||||
Subscriptions Page: Página de suscripciones
|
||||
Hide Channel Releases: Ocultar las nuevas publicaciones de los canales
|
||||
Hide Channel Podcasts: Ocultar los canales de podcasts
|
||||
Hide Subscriptions Shorts: Ocultar las suscripciones para los vídeos cortos
|
||||
Hide Subscriptions Videos: Ocultar las suscripciones de los Vídeos
|
||||
Hide Subscriptions Live: Ocultar las suscripciones de los directos
|
||||
The app needs to restart for changes to take effect. Restart and apply change?: ¿Quieres
|
||||
reiniciar FreeTube ahora para aplicar los cambios?
|
||||
Proxy Settings:
|
||||
|
@ -632,9 +649,16 @@ Channel:
|
|||
This channel does not currently have any live streams: Este canal no tiene actualmente
|
||||
ninguna retransmisión en directo
|
||||
Shorts:
|
||||
Shorts: Cortos
|
||||
This channel does not currently have any shorts: Este canal no tiene actualmente
|
||||
ningún vídeo corto
|
||||
Podcasts:
|
||||
Podcasts: Podcasts
|
||||
This channel does not currently have any podcasts: Este canal actualmente no tiene
|
||||
podcasts
|
||||
Releases:
|
||||
Releases: Publicaciones
|
||||
This channel does not currently have any releases: Este canal no tiene actualmente
|
||||
ninguna publicación
|
||||
Video:
|
||||
Mark As Watched: 'Marcar como visto'
|
||||
Remove From History: 'Borrar del historial'
|
||||
|
@ -955,10 +979,12 @@ Tooltips:
|
|||
una caché para la imagen en la memoria personalizada. Esto aumentará el uso
|
||||
de la memoria RAM.
|
||||
Distraction Free Settings:
|
||||
Hide Channels: Introduzca un nombre o ID de canal para ocultar todos los vídeos,
|
||||
listas de reproducción y el propio canal y evitar que aparezcan en las búsquedas
|
||||
o en las tendencias. El nombre del canal introducido debe coincidir completamente
|
||||
y distingue entre mayúsculas y minúsculas.
|
||||
Hide Channels: Ingresa un nombre del canal o un ID del canal para ocultar todos
|
||||
los videos, listas de reproducción y el propio canal para que no aparezcan en
|
||||
la búsqueda, tendencias, más populares y recomendados. El nombre del canal ingresado
|
||||
debe ser una coincidencia completa y distinguir entre mayúsculas y minúsculas.
|
||||
Hide Subscriptions Live: Esta configuración se reemplaza por la configuración
|
||||
«{appWideSetting}» de toda la aplicación, en la sección «{subsection}» de «{settingsSection}»
|
||||
SponsorBlock Settings:
|
||||
UseDeArrowTitles: Sustituye los títulos de los vídeos por títulos enviados por
|
||||
los usuarios desde DeArrow.
|
||||
|
@ -996,7 +1022,7 @@ Channels:
|
|||
Empty: Tu lista de canales está actualmente vacía.
|
||||
Unsubscribe: Cancelar la suscripción
|
||||
Unsubscribed: '{channelName} ha sido eliminado de tus suscripciones'
|
||||
Unsubscribe Prompt: ¿Está seguro de querer desuscribirse de "{channelName}"?
|
||||
Unsubscribe Prompt: ¿Está seguro/segura de querer desuscribirse de «{channelName}»?
|
||||
Age Restricted:
|
||||
Type:
|
||||
Channel: Canal
|
||||
|
|
|
@ -29,6 +29,13 @@ Close: 'Sulge'
|
|||
Back: 'Tagasi'
|
||||
Forward: 'Edasi'
|
||||
|
||||
# Global
|
||||
# Anything shared among components / views should be put here
|
||||
Global:
|
||||
Videos: 'Videod'
|
||||
Shorts: Lühivideod
|
||||
Live: Otseeeter
|
||||
|
||||
Version {versionNumber} is now available! Click for more details: 'Versioon {versionNumber}
|
||||
in nüüd saadaval! Lisateavet leiad siit'
|
||||
Download From Site: 'Laadi veebisaidist alla'
|
||||
|
@ -89,6 +96,9 @@ Subscriptions:
|
|||
Disabled Automatic Fetching: Sa oled lülitanud välja automaatse tellimuste laadimise.
|
||||
Uuendatud teabe nägemiseks laadi vaade uuesti.
|
||||
Empty Channels: Sinu tellitud kanalites hetkel pole ühtegi videot.
|
||||
Subscriptions Tabs: Tellimuste vahekaardid
|
||||
All Subscription Tabs Hidden: Kõik tellimuse vahekaardid on peidetud. Siinse sisu
|
||||
nägemiseks palun eemalda kaartide peitmine jaotises „{subsection}“ / „{settingsSection}“.
|
||||
Trending:
|
||||
Trending: 'Populaarsust koguvad videod'
|
||||
Trending Tabs: Populaarsust koguvad kaardid
|
||||
|
@ -274,6 +284,8 @@ Settings:
|
|||
Skip by Scrolling Over Video Player: Jäta vahele, kerides üle videopleieri
|
||||
Allow DASH AV1 formats: Luba DASH AV1 vormingud
|
||||
Enter Fullscreen on Display Rotate: Ekraani pööramisel ava täisekraanivaade
|
||||
Comment Auto Load:
|
||||
Comment Auto Load: Kommentaaride automaatne laadimine
|
||||
Privacy Settings:
|
||||
Privacy Settings: 'Privaatsuse seadistused'
|
||||
Remember History: 'Jäta ajalugu meelde'
|
||||
|
@ -369,6 +381,21 @@ Settings:
|
|||
Hide Channels Placeholder: Kanali nimi või tunnus
|
||||
Display Titles Without Excessive Capitalisation: Näita pealkirju ilma liigsete
|
||||
suurtähtedeta
|
||||
Sections:
|
||||
General: Üldist
|
||||
Side Bar: Külgpaan
|
||||
Channel Page: Kanali vaade
|
||||
Watch Page: Videovaade
|
||||
Subscriptions Page: Tellimuste vaade
|
||||
Hide Featured Channels: Peaida soovitatud kanalid
|
||||
Hide Channel Playlists: Peida kanali esitusloendid
|
||||
Hide Channel Shorts: Peida kanali lühivideod
|
||||
Hide Channel Community: Peida kanali kogukond
|
||||
Hide Channel Podcasts: Peida kanali taskuhäälingud
|
||||
Hide Channel Releases: Peida kanali väljalasked
|
||||
Hide Subscriptions Videos: Peida tellimuste videod
|
||||
Hide Subscriptions Shorts: Peida tellimuste lühivideod
|
||||
Hide Subscriptions Live: Peida tellimuste otse-eetrid
|
||||
Proxy Settings:
|
||||
Error getting network information. Is your proxy configured properly?: Võrguteavet
|
||||
ei õnnestu leida. Kas sa oled puhverserveri ikka korralikult seadistanud?
|
||||
|
@ -389,10 +416,10 @@ Settings:
|
|||
rakendus vajab muudatuste jõustamiseks uuesti käivitamist. Kas teeme seda nüüd?
|
||||
SponsorBlock Settings:
|
||||
Notify when sponsor segment is skipped: Anna teada, kui toetajate vaade jääb vahele
|
||||
'SponsorBlock API Url (Default is https://sponsor.ajay.app)': SponsorBlock'i API
|
||||
URL (vaikimisi https://sponsor.ajay.app)
|
||||
Enable SponsorBlock: Kasuta SponsorBlock'i
|
||||
SponsorBlock Settings: SponsorBlock seadistused
|
||||
'SponsorBlock API Url (Default is https://sponsor.ajay.app)': Sponsorite blokeerija
|
||||
API URL (vaikimisi https://sponsor.ajay.app)
|
||||
Enable SponsorBlock: Kasuta sponsorite blokeerijat
|
||||
SponsorBlock Settings: Sponsorite blokeerija seadistused
|
||||
Skip Options:
|
||||
Auto Skip: Automaatne vahelejätmine
|
||||
Show In Seek Bar: Näita otsinguribal
|
||||
|
@ -400,6 +427,7 @@ Settings:
|
|||
Skip Option: Jäta valik vahele
|
||||
Do Nothing: Ära tee midagi
|
||||
Category Color: Kategooria värv
|
||||
UseDeArrowTitles: Laadi video pealkirjad DeArrow teenusest
|
||||
External Player Settings:
|
||||
External Player: Väline meediamängija
|
||||
External Player Settings: Välise meediamängija seadistused
|
||||
|
@ -424,7 +452,7 @@ Settings:
|
|||
Experimental Settings:
|
||||
Experimental Settings: Katselised seadistused
|
||||
Warning: Tegemist on katseliste seadistustega ja sisselülitamisel võivad põhjustada
|
||||
rakenduse kokkujooksmist. Kindlasti ära unusta oma andmete vatundamist. Jätka
|
||||
rakenduse kokkujooksmist. Kindlasti ära unusta oma andmete varundamist. Jätka
|
||||
omal vastutusel!
|
||||
Replace HTTP Cache: Asenda HTTP vahemälu
|
||||
Password Dialog:
|
||||
|
@ -542,6 +570,35 @@ Channel:
|
|||
About: 'Kanali teave'
|
||||
Channel Description: 'Kanali kirjeldus'
|
||||
Featured Channels: 'Soovitatud kanalid'
|
||||
Details: Üksikasjad
|
||||
Location: Asukoht
|
||||
Tags:
|
||||
Search for: Otsi silti „{tag}“
|
||||
Tags: Sildid
|
||||
Joined: Liitunud
|
||||
This channel does not allow searching: See kanal ei luba otsingu kasutamist
|
||||
Channel Tabs: Kanali kaardid
|
||||
Shorts:
|
||||
This channel does not currently have any shorts: Sellel kanalil pole lühivideosid
|
||||
Live:
|
||||
Live: Otseeeter
|
||||
This channel does not currently have any live streams: Sellel kanalil pole hetkel
|
||||
ühtegi otseeetrit
|
||||
Community:
|
||||
Community: Kogukond
|
||||
This channel currently does not have any posts: Sellel kanalil pole hetkel postitusi
|
||||
This channel does not exist: Sellist kanalit ei leidu
|
||||
This channel is age-restricted and currently cannot be viewed in FreeTube.: Sellel
|
||||
kanalil on vanusega seotud piirangud ja teda ei saa hetkel FreeTube'i vahendusel
|
||||
vaadata.
|
||||
Podcasts:
|
||||
Podcasts: Taskuhäälingud
|
||||
This channel does not currently have any podcasts: Sellel kanalil hetkel pole
|
||||
taskuhäälinguid
|
||||
Releases:
|
||||
This channel does not currently have any releases: Sellel kanalil pole hetkel
|
||||
ühtegi väljalaset
|
||||
Releases: Väljalasked
|
||||
Video:
|
||||
Mark As Watched: 'Märgi vaadatuks'
|
||||
Remove From History: 'Kustuta ajaloost'
|
||||
|
@ -568,7 +625,7 @@ Video:
|
|||
värskenda uuesti kontrollimiseks lehte'
|
||||
# As in a Live Video
|
||||
Live: 'Otse eetris'
|
||||
Live Now: 'Otse eetris'
|
||||
Live Now: 'Hetkel otseeetris'
|
||||
Live Chat: 'Vestlus reaalajas'
|
||||
Enable Live Chat: 'Luba reaalajas vestlust'
|
||||
Live Chat is currently not supported in this build.: 'Reaalajas vestlus ei ole selles
|
||||
|
@ -682,6 +739,8 @@ Video:
|
|||
Show Super Chat Comment: Näita Super Chat'i kommentaare
|
||||
Scroll to Bottom: Keri alla
|
||||
Upcoming: Tulemas
|
||||
'Live Chat is unavailable for this stream. It may have been disabled by the uploader.': Otsevestlus
|
||||
pole selle videovoo puhul saadaval. Võib-olla on üleslaadija vestluse keelanud.
|
||||
Videos:
|
||||
#& Sort By
|
||||
Sort By:
|
||||
|
@ -794,9 +853,9 @@ Tooltips:
|
|||
järjekorra pööramine) ei toeta siis ära näita hoiatusi.
|
||||
Custom External Player Arguments: Semikoolonitega (;) eraldatud käsurea argumendid,
|
||||
mida sa soovid välisele meediamängijale saata.
|
||||
External Player: "Seadistades välise meediamängija kuvame pisipildil ikooni video\
|
||||
\ (või esitusloendi) esitamiseks välises meediamängijas. Hoiatus: Invidious'e\
|
||||
\ seadistused ei mõjuta välise meediamängija kasutamist."
|
||||
External Player: "Seadistades välise meediamängija kuvame pisipildil ikooni video
|
||||
(või esitusloendi) esitamiseks välises meediamängijas. Hoiatus: Invidious'e
|
||||
seadistused ei mõjuta välise meediamängija kasutamist."
|
||||
DefaultCustomArgumentsTemplate: "(Vaikimisi: '{defaultCustomArguments}')"
|
||||
Subscription Settings:
|
||||
Fetch Feeds from RSS: Selle valiku kasutamisel FreeTube pruugib tellimuste andmete
|
||||
|
@ -818,8 +877,8 @@ Tooltips:
|
|||
kasutamist.
|
||||
Invidious Instance: Invidious'e teenuse server, mida FreeTube kasutab API kutse
|
||||
tegemisel.
|
||||
External Link Handling: "Vali vaikekäitumine, kui vajutatakse linki, mida ei saa\
|
||||
\ avada FreeTubes.\nVaikimisi avaneb link kasutaja vaikebrauseris.\n"
|
||||
External Link Handling: "Vali vaikekäitumine, kui vajutatakse linki, mida ei saa
|
||||
avada FreeTubes.\nVaikimisi avaneb link kasutaja vaikebrauseris.\n"
|
||||
Player Settings:
|
||||
Default Video Format: Vali kasutatavad videovormingud. DASH-vormingutel on üldjuhul
|
||||
parem kvaliteet. Pärandvormingute kvaliteedi ülempiir on 720p ja seetõttu kasutavad
|
||||
|
@ -844,12 +903,18 @@ Tooltips:
|
|||
hiire ratast.
|
||||
Distraction Free Settings:
|
||||
Hide Channels: Sisesta kanali nimi või kanali ID, et kõik videod, esitusloendid
|
||||
ja kanal ise ei oleks nähtav otsingus ega populaarsust koguvate videote vaates.
|
||||
Sisestatud kanali nimi peab vastama täielikult ja on tõstutundlik.
|
||||
ja kanal ise ei oleks nähtav otsingus, soovitatavate videote, populaarsete videote
|
||||
ja populaarsust koguvate videote vaates. Sisestatud kanali nimi peab otsingule
|
||||
vastama täielikult ja on tõstutundlik.
|
||||
Hide Subscriptions Live: Selle seadistuse tühistab rakenduseülene „{appWideSetting}“
|
||||
seadistus „{subsection}“/„{settingsSection}“
|
||||
Experimental Settings:
|
||||
Replace HTTP Cache: Sellega lülitatakse välja Electron'i standardne kettal paiknev
|
||||
http-puhver ja võetakse kasutusele rakenduse mälupõhine puhver. Üheks tulemuseks
|
||||
saab olema suurem mälukasutus.
|
||||
SponsorBlock Settings:
|
||||
UseDeArrowTitles: Asenda video nimi kasutajate poolt DeArrow teenusesse lisatud
|
||||
nimega (pealkirjaga).
|
||||
Playing Next Video Interval: Kohe esitan järgmist videot. Tühistamiseks klõpsi. |
|
||||
{nextVideoInterval} sekundi möödumisel esitan järgmist videot. Tühistamiseks klõpsi.
|
||||
| {nextVideoInterval} sekundi möödumisel esitan järgmist videot. Tühistamiseks klõpsi.
|
||||
|
@ -894,3 +959,7 @@ Chapters:
|
|||
praegune peatükk: {chapterName}'
|
||||
Ok: Sobib
|
||||
Preferences: Eelistused
|
||||
Hashtag:
|
||||
Hashtag: Teemaviide
|
||||
This hashtag does not currently have any videos: Selle teemaviite ehk haaksõna alusel
|
||||
ei leidu hetkel ühtegi videot
|
||||
|
|
|
@ -29,6 +29,11 @@ Close: 'Itxi'
|
|||
Back: 'Atzera'
|
||||
Forward: 'Aurrera'
|
||||
|
||||
# Global
|
||||
# Anything shared among components / views should be put here
|
||||
Global:
|
||||
Videos: 'Bideoak'
|
||||
|
||||
Version {versionNumber} is now available! Click for more details: '{versionNumber}
|
||||
bertsioa erabilgarri! Klikatu azalpen gehiagorako'
|
||||
Download From Site: 'Webgunetik jaitsi'
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
FreeTube: 'FreeTube'
|
||||
# Currently on Subscriptions, Playlists, and History
|
||||
'This part of the app is not ready yet. Come back later when progress has been made.': >-
|
||||
Tämä sovelluksen osa ei ole vielä valmis. Tule takaisin myöhemmin.
|
||||
Tämä sovelluksen osa ei ole vielä valmis. Tule takaisin myöhemmin kun olemme edistyneet.
|
||||
|
||||
# Webkit Menu Bar
|
||||
File: 'Tiedosto'
|
||||
Quit: 'Poistu'
|
||||
Quit: 'Lopeta'
|
||||
Edit: 'Muokkaa'
|
||||
Undo: 'Kumoa'
|
||||
Redo: 'Tee uudelleen'
|
||||
|
@ -15,18 +15,25 @@ Paste: 'Liitä'
|
|||
Delete: 'Poista'
|
||||
Select all: 'Valitse kaikki'
|
||||
Reload: 'Lataa uudelleen'
|
||||
Force Reload: 'Pakota uudelleenlataus'
|
||||
Toggle Developer Tools: 'Kehittäjän työkalut'
|
||||
Force Reload: 'Pakota Uudelleenlataus'
|
||||
Toggle Developer Tools: 'Vaihda Kehittäjän Työkaluihin'
|
||||
Actual size: 'Todellinen koko'
|
||||
Zoom in: 'Lähennä'
|
||||
Zoom out: 'Loitonna'
|
||||
Toggle fullscreen: 'Koko näytön tila'
|
||||
Toggle fullscreen: 'Vaihda koko näyttöön'
|
||||
Window: 'Ikkuna'
|
||||
Minimize: 'Pienennä'
|
||||
Close: 'Sulje'
|
||||
Back: 'Takaisin'
|
||||
Forward: 'Eteenpäin'
|
||||
|
||||
# Global
|
||||
# Anything shared among components / views should be put here
|
||||
Global:
|
||||
Videos: 'Videot'
|
||||
Shorts: Lyhyet
|
||||
Live: Livenä
|
||||
|
||||
# Search Bar
|
||||
Search / Go to URL: 'Etsi / Mene osoitteeseen'
|
||||
# In Filter Button
|
||||
|
@ -81,6 +88,10 @@ Subscriptions:
|
|||
Empty Channels: Tilaamillasi kanavilla ei ole videoita tällä hetkellä.
|
||||
Disabled Automatic Fetching: Olet poistanut käytöstä automaattisen tilaustennoutamisen.
|
||||
Virkistä tilaukset nähdäksesi ne täällä.
|
||||
Subscriptions Tabs: Tilaukset-välilehdet
|
||||
All Subscription Tabs Hidden: Kaikki tilausvälilehdet on piilotettu. Jos haluat
|
||||
nähdä sisällön täällä, poista joitakin välilehtiä ”{settingsSection}”-osion ”{settingsSection}”-osion
|
||||
”{subsection}”-välilehdistä.
|
||||
Trending:
|
||||
Trending: 'Nousussa'
|
||||
Trending Tabs: Nousussa olevat välilehdet
|
||||
|
@ -214,9 +225,9 @@ Settings:
|
|||
Force Local Backend for Legacy Formats: 'Pakota paikallinen taustaohjelma vanhoille
|
||||
formaateille'
|
||||
Remember History: 'Muista historia'
|
||||
Play Next Video: 'Toista seuraava video'
|
||||
Play Next Video: 'Toista Seuraava Video'
|
||||
Turn on Subtitles by Default: 'Ota tekstitys käyttöön oletusarvoisesti'
|
||||
Autoplay Videos: 'Toista seuraava video'
|
||||
Autoplay Videos: 'Toista Automaattisesti Videot'
|
||||
Proxy Videos Through Invidious: 'Välitä videot Invidiousin kautta'
|
||||
Autoplay Playlists: 'Toista soittolistat automaattisesti'
|
||||
Enable Theatre Mode by Default: 'Ota teatteritila käyttöön oletusarvoisesti'
|
||||
|
@ -391,7 +402,7 @@ Settings:
|
|||
Hide Sharing Actions: Piilota jakamistoiminnot
|
||||
Hide Chapters: Piilota kappaleet
|
||||
Hide Channels: Piilota videot kanavilta
|
||||
Hide Upcoming Premieres: Piilota tulevat ensiesitykset
|
||||
Hide Upcoming Premieres: Piilota Tulevat Ensiesitykset
|
||||
Hide Channels Placeholder: Kanavan nimi tai tunnus
|
||||
Display Titles Without Excessive Capitalisation: Näytä otsikot ilman liiallista
|
||||
isoja kirjaimia
|
||||
|
@ -404,6 +415,11 @@ Settings:
|
|||
Channel Page: Kanavan sivu
|
||||
Watch Page: Katso sivu
|
||||
General: Yleiset
|
||||
Subscriptions Page: Tilaukset-sivu
|
||||
Hide Channel Releases: Piilota kanavajulkaisut
|
||||
Hide Channel Podcasts: Piilota kanavan podcastit
|
||||
Hide Subscriptions Videos: Piilota tilausvideot
|
||||
Hide Subscriptions Live: Piilota tilausten livet
|
||||
The app needs to restart for changes to take effect. Restart and apply change?: Sovellus
|
||||
on käynnistettävä uudelleen, jotta muutokset tulevat voimaan. Käynnistetäänkö
|
||||
uudelleen?
|
||||
|
@ -436,6 +452,7 @@ Settings:
|
|||
Do Nothing: Älä tee mitään
|
||||
Prompt To Skip: Ehdota ohittamista
|
||||
Category Color: Luokan väri
|
||||
UseDeArrowTitles: Käytä DeArrow Video Otsikoita
|
||||
External Player Settings:
|
||||
Custom External Player Arguments: Omavalintaisen ulkoisen toisto-ohjelman määritykset
|
||||
Custom External Player Executable: Omavalintaisen ulkoisen toisto-ohjelman ajettava
|
||||
|
@ -460,9 +477,9 @@ Settings:
|
|||
Hide Search Bar: Piilota hakupalkki
|
||||
Experimental Settings:
|
||||
Experimental Settings: Kokeelliset asetukset
|
||||
Warning: Nämä asetukset ovat kokeellisia ja ne aiheuttavat kaatumisia, kun ne
|
||||
ovat käytössä. Varmuuskopioiden tekeminen on erittäin suositeltavaa. Käytä omalla
|
||||
vastuullasi!
|
||||
Warning: Nämä asetukset ovat kokeellisia ja ne voivat aiheuttavat kaatumisia,
|
||||
kun ne ovat käytössä. Varmuuskopioiden tekeminen on erittäin suositeltavaa.
|
||||
Käytä omalla vastuullasi!
|
||||
Replace HTTP Cache: Korvaa HTTP-välimuisti
|
||||
Password Dialog:
|
||||
Password: Salasana
|
||||
|
@ -578,6 +595,17 @@ Channel:
|
|||
Live: Livenä
|
||||
This channel does not currently have any live streams: Tällä kanavalla ei ole
|
||||
tällä hetkellä yhtään suoraa lähetystä
|
||||
Shorts:
|
||||
This channel does not currently have any shorts: Tällä kanavalla ei juuri nyt
|
||||
ole lyhyitä
|
||||
Podcasts:
|
||||
Podcasts: Podcastit
|
||||
This channel does not currently have any podcasts: Tällä kanavalla ei ole yhtäkään
|
||||
podcastia
|
||||
Releases:
|
||||
Releases: Julkaisut
|
||||
This channel does not currently have any releases: Tällä kanavalla ei ole yhtäkään
|
||||
julkaisua
|
||||
Video:
|
||||
Open in YouTube: 'Avaa Youtubessa'
|
||||
Copy YouTube Link: 'Kopioi Youtube-linkki'
|
||||
|
@ -643,7 +671,7 @@ Video:
|
|||
Mark As Watched: Merkitse katsotuksi
|
||||
Autoplay: Automaattinen toisto
|
||||
Play Previous Video: Toista edellinen video
|
||||
Play Next Video: Toista seuraava video
|
||||
Play Next Video: Toista Seuraava Video
|
||||
Reverse Playlist: Käänteinen soittolista
|
||||
Shuffle Playlist: Sekoita soittolistaa
|
||||
Loop Playlist: Kierrätä soittolistaa
|
||||
|
@ -715,7 +743,7 @@ Video:
|
|||
Premieres on: Julkaistaan
|
||||
Premieres: Ensilähetykset
|
||||
Show Super Chat Comment: Näytä Super Chat -kommentti
|
||||
Scroll to Bottom: Vieritä alaspäin
|
||||
Scroll to Bottom: Vieritä Alaspäin
|
||||
Upcoming: Tuleva
|
||||
'Live Chat is unavailable for this stream. It may have been disabled by the uploader.': Live-chat
|
||||
ei ole käytettävissä tässä suoratoistossa. Lataaja on saattanut poistaa sen käytöstä.
|
||||
|
@ -778,8 +806,8 @@ Comments:
|
|||
Reply: 'Vastaa'
|
||||
There are no comments available for this video: 'Tähän videoon ei ole yhtään kommenttia'
|
||||
Load More Comments: 'Lataa lisää kommentteja'
|
||||
There are no more comments for this video: Ei enempää kommentteja
|
||||
No more comments available: Ei enempää kommentteja
|
||||
There are no more comments for this video: Ei ole enempää kommentteja tälle videolle
|
||||
No more comments available: Eiole enempää kommentteja
|
||||
Newest first: Uusimmat ensin
|
||||
Top comments: Suosituimmat kommentit
|
||||
Sort by: Lajitteluperuste
|
||||
|
@ -847,7 +875,7 @@ Profile:
|
|||
Add Selected To Profile: Lisää valitut profiiliin
|
||||
Delete Selected: Poista valitut
|
||||
Select None: Älä valitse mitään
|
||||
Select All: Valitse kaikki
|
||||
Select All: Valitse Kaikki
|
||||
'{number} selected': '{number} valittu'
|
||||
Other Channels: Muut kanavat
|
||||
Subscription List: Tilauslista
|
||||
|
@ -895,11 +923,12 @@ Tooltips:
|
|||
toiston nopeutta. Palataksesi alkuperäiseen toistonopeuteen (1x ellei toisin
|
||||
määritelty asetuksissa), pidä CTRL-painike (Komentopainike MAC-tietokoneessa)
|
||||
painettuna ja paina hiiren vasenta näppäintä.
|
||||
Skip by Scrolling Over Video Player: Käytä vierityspyörää videon selaamiseen MPV-tyyliin.
|
||||
Allow DASH AV1 formats: DASH AV1 -formaatit saattavat näyttää paremmalta kuin
|
||||
DASH H.264 -formaatit. DASH AV1 -formaatit vaativat enemmän tehoa toistamiseen!
|
||||
Ne eivät ole käytettävissä kaikissa videoissa, ja näissä tapauksissa soitin
|
||||
käyttää sen sijaan DASH H.264 -formaatteja.
|
||||
Skip by Scrolling Over Video Player: Käytä vieritysrullaa videon selaamiseen,
|
||||
MPV-tyyliin.
|
||||
Allow DASH AV1 formats: DASH AV1 formaatit saattavat näyttää paremmalta kuin DASH
|
||||
H.264 formaatit. DASH AV1 formaatit vaativat enemmän tehoa toistamiseen! Ne
|
||||
eivät ole käytettävissä kaikissa videoissa ja näissä tapauksissa soitin käyttää
|
||||
sen sijaan DASH H.264 formaatteja.
|
||||
Privacy Settings:
|
||||
Remove Video Meta Files: Kun tämä on kytkettynä päälle, FreeTube poistaa automaattisesti
|
||||
meta-tiedostot jotka luotiin videon toiston aikana, katselusivu suljettaessa.
|
||||
|
@ -922,8 +951,13 @@ Tooltips:
|
|||
käyttöä.
|
||||
Distraction Free Settings:
|
||||
Hide Channels: Anna kanavan nimi tai kanavatunnus piilottaaksesi kaikki videot,
|
||||
soittolistat ja itse kanavan näkymästä haussa tai trendaamisessa. Annetun kanavan
|
||||
nimen on vastattava täydellisesti ja kirjainkoolla on merkitystä.
|
||||
soittolistat ja itse kanavan näkymästä haussa, trendaamisessa, suosituimmissa
|
||||
ja suositelluissa. Annetun kanavan nimen on oltava täysin oikein ja kirjainkoolla
|
||||
on merkitystä.
|
||||
Hide Subscriptions Live: Tämä asetus ohitetaan koko sovelluksen laajuisella ”{appWideSetting}”-asetuksella,
|
||||
joka on ”{settingsSection}”-osion ”{subsection}”-osiossa.
|
||||
SponsorBlock Settings:
|
||||
UseDeArrowTitles: Korvaa videon otsikot käyttäjien lähettämillä DeArrow'n otsikoilla.
|
||||
More: Lisää
|
||||
Playing Next Video Interval: Seuraava video alkaa. Klikkaa peruuttaaksesi. |Seuraava
|
||||
video alkaa {nextVideoInterval} sekunnin kuluttua. Klikkaa peruuttaaksesi. | Seuraava
|
||||
|
@ -946,7 +980,7 @@ Downloading failed: Videon "{videoTitle}" lataamisessa havaittiin ongelma
|
|||
Downloading has completed: Videon "{videoTitle}" lataus on valmis
|
||||
Starting download: Aloitetaan lataamaan "{videoTitle}"
|
||||
Screenshot Success: Kuvakaappaus tallennettu nimellä ”{filePath}”
|
||||
New Window: Uusi ikkuna
|
||||
New Window: Uusi Ikkuna
|
||||
Age Restricted:
|
||||
This {videoOrPlaylist} is age restricted: Tämä {videoOrPlaylist} on ikärajoitettu
|
||||
Type:
|
||||
|
|
|
@ -28,6 +28,13 @@ Close: 'Fermer'
|
|||
Back: 'Retour'
|
||||
Forward: 'Avancer'
|
||||
|
||||
# Global
|
||||
# Anything shared among components / views should be put here
|
||||
Global:
|
||||
Videos: 'Vidéos'
|
||||
Shorts: Shorts
|
||||
Live: En direct
|
||||
|
||||
# Search Bar
|
||||
Search / Go to URL: 'Rechercher / ouvrir l''URL'
|
||||
# In Filter Button
|
||||
|
@ -87,6 +94,10 @@ Subscriptions:
|
|||
abonnements. Actualisez les abonnements pour les voir ici.
|
||||
Empty Channels: Les chaînes auxquelles vous êtes abonné(e) ne contiennent actuellement
|
||||
aucune vidéo.
|
||||
Subscriptions Tabs: Onglets Abonnements
|
||||
All Subscription Tabs Hidden: Tous les onglets d'abonnement sont cachés. Pour voir
|
||||
le contenu ici, veuillez désactiver certains onglets dans la section « {subsection} »
|
||||
dans « {settingsSection} ».
|
||||
Trending:
|
||||
Trending: 'Tendance'
|
||||
Trending Tabs: Onglets des Tendances
|
||||
|
@ -427,6 +438,12 @@ Settings:
|
|||
Side Bar: Barre latérale
|
||||
Watch Page: Page de lecture
|
||||
General: Général
|
||||
Subscriptions Page: Page Abonnements
|
||||
Hide Channel Podcasts: Masquer les podcasts de la chaîne
|
||||
Hide Channel Releases: Masquer les publications de la chaîne
|
||||
Hide Subscriptions Videos: Masquer les vidéos des abonnements
|
||||
Hide Subscriptions Shorts: Masquer les shorts des abonnements
|
||||
Hide Subscriptions Live: Masquer les diffusions en direct des abonnements
|
||||
The app needs to restart for changes to take effect. Restart and apply change?: L'application
|
||||
doit être redémarrée pour que les changements prennent effet. Redémarrer et appliquer
|
||||
les changements ?
|
||||
|
@ -623,9 +640,16 @@ Channel:
|
|||
This channel does not currently have any live streams: Cette chaîne n'a actuellement
|
||||
aucun flux en direct
|
||||
Shorts:
|
||||
Shorts: Shorts
|
||||
This channel does not currently have any shorts: Cette chaîne n'a actuellement
|
||||
aucun shorts
|
||||
Releases:
|
||||
Releases: Publications
|
||||
This channel does not currently have any releases: Cette chaîne n'a actuellement
|
||||
aucune publication
|
||||
Podcasts:
|
||||
Podcasts: Podcasts
|
||||
This channel does not currently have any podcasts: Cette chaîne n'a pas encore
|
||||
de podcasts
|
||||
Video:
|
||||
Mark As Watched: 'Marquer comme vu'
|
||||
Remove From History: 'Retirer de l''historique'
|
||||
|
@ -999,8 +1023,12 @@ Tooltips:
|
|||
Distraction Free Settings:
|
||||
Hide Channels: Entrez un nom de chaîne ou un identifiant de chaîne pour empêcher
|
||||
toutes les vidéos, les listes de lecture et la chaîne elle-même d'apparaître
|
||||
dans les recherches ou les tendances. Le nom du canal entré doit être une correspondance
|
||||
complète et est sensible à la casse.
|
||||
dans les recherches, dans les catégories Tendances, Plus populaires et Recommandés.
|
||||
Le nom de la chaîne entré doit correspondre exactement et est sensible à la
|
||||
casse.
|
||||
Hide Subscriptions Live: Ce paramètre est remplacé par le paramètre « {appWideSetting} »
|
||||
applicable à l'ensemble de l'application, dans la section « {subsection} » de
|
||||
la section « {settingsSection} »
|
||||
SponsorBlock Settings:
|
||||
UseDeArrowTitles: Remplacez les titres des vidéos par des titres proposés par
|
||||
les utilisateurs de DeArrow.
|
||||
|
|
|
@ -30,6 +30,11 @@ Close: 'Pechar'
|
|||
Back: 'Atrás'
|
||||
Forward: 'Adiante'
|
||||
|
||||
# Global
|
||||
# Anything shared among components / views should be put here
|
||||
Global:
|
||||
Videos: 'Vídeos'
|
||||
|
||||
Version {versionNumber} is now available! Click for more details: 'A versión {versionNumber}
|
||||
está dispoñible! Fai clic para veres máis detalles'
|
||||
Download From Site: 'Descargar do sitio'
|
||||
|
|
|
@ -29,6 +29,13 @@ Close: 'סגירה'
|
|||
Back: 'אחורה'
|
||||
Forward: 'קדימה'
|
||||
|
||||
# Global
|
||||
# Anything shared among components / views should be put here
|
||||
Global:
|
||||
Videos: 'סרטונים'
|
||||
Shorts: Shorts
|
||||
Live: חי
|
||||
|
||||
Version {versionNumber} is now available! Click for more details: 'גרסה {versionNumber}
|
||||
זמינה מעתה! לחיצה תציג פרטים נוספים'
|
||||
Download From Site: 'הורדה מהאתר'
|
||||
|
@ -88,6 +95,9 @@ Subscriptions:
|
|||
Disabled Automatic Fetching: השבתת משיכת מינויים אוטומטית. יש לרענן את המינויים
|
||||
כדי לצפות בהם כאן.
|
||||
Empty Channels: בערוצים אליהם נרשמת אין כלל סרטונים.
|
||||
All Subscription Tabs Hidden: כל לשוניות המינויים מותסרות. כדי לראות את התוכן כאן,
|
||||
נא לבטל את הסתרתן של כמה מהלשוניות תחת הסעיף „{subsection}” שב„{settingsSection}”.
|
||||
Subscriptions Tabs: לשוניות מינויים
|
||||
Trending:
|
||||
Trending: 'הסרטונים החמים'
|
||||
Trending Tabs: לשוניות מובילים
|
||||
|
@ -398,6 +408,12 @@ Settings:
|
|||
Channel Page: עמוד ערוץ
|
||||
Side Bar: סרגל צד
|
||||
General: כללי
|
||||
Subscriptions Page: עמוד מינויים
|
||||
Hide Channel Podcasts: הסתרת הסכתים של הערוץ
|
||||
Hide Channel Releases: הסתרת שחרורים של הערוץ
|
||||
Hide Subscriptions Live: הסתרת שידורים חיים של המינויים
|
||||
Hide Subscriptions Shorts: הסתרת Shorts של המינויים
|
||||
Hide Subscriptions Videos: הסתרת סרטוני מינוי
|
||||
The app needs to restart for changes to take effect. Restart and apply change?: צריך
|
||||
להפעיל את היישומון מחדש כדי שהשינויים ייכנסו לתוקף. להפעיל מחדש ולהחיל את השינוי?
|
||||
Proxy Settings:
|
||||
|
@ -620,8 +636,13 @@ Channel:
|
|||
This channel does not currently have any live streams: לערוץ הזה אין שידורים חיים
|
||||
כרגע
|
||||
Shorts:
|
||||
Shorts: Shorts
|
||||
This channel does not currently have any shorts: אין כרגע Shorts בערוץ הזה
|
||||
Podcasts:
|
||||
Podcasts: הסכתים
|
||||
This channel does not currently have any podcasts: בערוץ הזה אין הסכתים כרגע
|
||||
Releases:
|
||||
Releases: שחרורים
|
||||
This channel does not currently have any releases: בערוץ הזה אין שחרורים כרגע
|
||||
Video:
|
||||
Mark As Watched: 'סמנו כנצפה'
|
||||
Remove From History: 'מחקו מהיסטוריית הצפייה'
|
||||
|
@ -913,8 +934,10 @@ Tooltips:
|
|||
אישית בזיכרון. יגדיל את צריכת הזיכרון (RAM).
|
||||
Distraction Free Settings:
|
||||
Hide Channels: יש למלא את שם או מזהה הערוץ כדי להסתיר את כל הסרטונים, רשימות הנגינה
|
||||
ואת הערוץ עצמו כך שלא יופיע בחיפוש או במובילים. שם הערוץ שמילאת צריך להיות תואם
|
||||
במלואו ותואם מבחינת רישיות (אותיות גדולות/קטנות).
|
||||
ואת הערוץ עצמו כך שלא יופיע בחיפוש, במובילים, בנפוצים ביותר או במומלצים. שם
|
||||
הערוץ שמילאת צריך להיות תואם במלואו ותואם מבחינת רישיות (אותיות גדולות/קטנות).
|
||||
Hide Subscriptions Live: הגדרה זו נדרסת על ידי ההגדרה הכללית „{appWideSetting}”,
|
||||
בסעיף „{subsection}” שב„{settingsSection}”
|
||||
SponsorBlock Settings:
|
||||
UseDeArrowTitles: החלפת כותרות הסרטונים עם כותרות ששלחו משתמשים ב־DeArrow.
|
||||
More: עוד
|
||||
|
|
|
@ -29,6 +29,13 @@ Close: 'Zatvori'
|
|||
Back: 'Natrag'
|
||||
Forward: 'Naprijed'
|
||||
|
||||
# Global
|
||||
# Anything shared among components / views should be put here
|
||||
Global:
|
||||
Videos: 'Videa'
|
||||
Shorts: Kratka videa
|
||||
Live: Uživo
|
||||
|
||||
# Search Bar
|
||||
Search / Go to URL: 'Pretraži / Idi na URL'
|
||||
# In Filter Button
|
||||
|
@ -408,6 +415,8 @@ Settings:
|
|||
General: Opće
|
||||
Side Bar: Bočna traka
|
||||
Channel Page: Stranica kanala
|
||||
Hide Channel Podcasts: Sakrij kanal podcastova
|
||||
Hide Channel Releases: Sakrij kanal izdanja
|
||||
The app needs to restart for changes to take effect. Restart and apply change?: Promjene
|
||||
će se primijeniti nakon ponovnog pokeretanja programa. Ponovo pokrenuti program?
|
||||
Proxy Settings:
|
||||
|
@ -439,6 +448,7 @@ Settings:
|
|||
Prompt To Skip: Poziv za preskakanje
|
||||
Do Nothing: Ne čini ništa
|
||||
Category Color: Boja kategorije
|
||||
UseDeArrowTitles: Koristi DeArrow Video naslove
|
||||
External Player Settings:
|
||||
Custom External Player Arguments: Argumenti prilagođenog vanjskog playera
|
||||
Custom External Player Executable: Izvršna datoteka prilagođenog vanjskog playera
|
||||
|
@ -635,7 +645,14 @@ Channel:
|
|||
Shorts:
|
||||
This channel does not currently have any shorts: Ovaj kanal trenutačno nema kratka
|
||||
videa
|
||||
Shorts: Kratka videa
|
||||
Releases:
|
||||
Releases: Izdanja
|
||||
This channel does not currently have any releases: Ovaj kanal trenutačno nema
|
||||
izdanja
|
||||
Podcasts:
|
||||
Podcasts: Podcasti
|
||||
This channel does not currently have any podcasts: Ovaj kanal trenutačno nema
|
||||
podcastova
|
||||
Video:
|
||||
Mark As Watched: 'Označi kao pogledano'
|
||||
Remove From History: 'Ukloni iz povijesti'
|
||||
|
@ -948,8 +965,10 @@ Tooltips:
|
|||
i aktivira prilagođenu predmemoriju slika u memoriji. Povećava korištenje RAM-a.
|
||||
Distraction Free Settings:
|
||||
Hide Channels: Upiši ime kanala ili ID kanala za skrivanje svih videa, zbirki
|
||||
kao i sam kanal u pretrazi ili u trendovima. Upisano ime kanala se mora potpuno
|
||||
poklapati i razlikuje velika i mala slova.
|
||||
kao i samog kanala u pretrazi, trendovima popularnim i preporučenim. Upisano
|
||||
ime kanala se mora potpuno poklapati i razlikuje velika i mala slova.
|
||||
SponsorBlock Settings:
|
||||
UseDeArrowTitles: Zamijeni naslove videa koje su poslali korisnici s DeArrow naslovima.
|
||||
Playing Next Video Interval: Trenutna reprodukcija sljedećeg videa. Pritisni za prekid.
|
||||
| Reprodukcija sljedećeg videa za {nextVideoInterval} sekunde. Pritisni za prekid.
|
||||
| Reprodukcija sljedećeg videa za {nextVideoInterval} sekundi. Pritisni za prekid.
|
||||
|
|
|
@ -30,6 +30,13 @@ Close: 'Bezárás'
|
|||
Back: 'Vissza'
|
||||
Forward: 'Előre'
|
||||
|
||||
# Global
|
||||
# Anything shared among components / views should be put here
|
||||
Global:
|
||||
Videos: 'Videók'
|
||||
Shorts: Rövidfilmek
|
||||
Live: Élő
|
||||
|
||||
Version {versionNumber} is now available! Click for more details: 'A(z) {versionNumber}
|
||||
verzió már elérhető! Kattintson a további részletekért'
|
||||
Download From Site: 'Letöltés a webhelyről'
|
||||
|
@ -92,6 +99,10 @@ Subscriptions:
|
|||
Disabled Automatic Fetching: Az önműködő feliratkozási kérés letiltva. Frissítse
|
||||
a feliratkozást a megtekintéséhez.
|
||||
Empty Channels: A feliratkozott csatornák jelenleg nem tartalmaznak videókat.
|
||||
All Subscription Tabs Hidden: Az összes feliratkozási lap el van rejtve. Az itteni
|
||||
tartalom megtekintéséhez, kérjük, jelenítse meg néhány lap elrejtését a(z) „{settingsSection}”
|
||||
„{subsection}” szakaszában.
|
||||
Subscriptions Tabs: Feliratkozások lapok
|
||||
Trending:
|
||||
Trending: 'Népszerű'
|
||||
Trending Tabs: Népszerű lapok
|
||||
|
@ -416,6 +427,12 @@ Settings:
|
|||
Channel Page: Csatornalap
|
||||
Watch Page: Nézőlap
|
||||
General: Általános
|
||||
Subscriptions Page: Feliratkozások oldal
|
||||
Hide Channel Podcasts: Csatornapodcastok elrejtése
|
||||
Hide Channel Releases: Csatornakiadások elrejtése
|
||||
Hide Subscriptions Shorts: Feliratkozások rövidfilmek elrejtése
|
||||
Hide Subscriptions Videos: Feliratkozási videók elrejtése
|
||||
Hide Subscriptions Live: Feliratkozások élők elrejtése
|
||||
The app needs to restart for changes to take effect. Restart and apply change?: Az
|
||||
alkalmazásnak újra kell indulnia, hogy a változtatások életbe lépjenek. Indítsa
|
||||
újra és alkalmazza a módosítást?
|
||||
|
@ -638,11 +655,18 @@ Channel:
|
|||
bejegyzések
|
||||
Community: Közösség
|
||||
Shorts:
|
||||
Shorts: Rövidfilmek
|
||||
This channel does not currently have any shorts: Ezen a csatornán jelenleg nincsenek
|
||||
rövidfilmek
|
||||
This channel does not exist: Nem létezik ez a csatorna
|
||||
This channel does not allow searching: Keresés nem engedélyezett ezen a csatornán
|
||||
Releases:
|
||||
Releases: Kiadások
|
||||
This channel does not currently have any releases: Ennek a csatornának jelenleg
|
||||
nincsenek kiadásai
|
||||
Podcasts:
|
||||
Podcasts: Podcastok
|
||||
This channel does not currently have any podcasts: Ez a csatorna jelenleg nem
|
||||
rendelkezik podcastokkal
|
||||
Video:
|
||||
Mark As Watched: 'Megjelölés megtekintettként'
|
||||
Remove From History: 'Eltávolítás az előzményekből'
|
||||
|
@ -950,8 +974,10 @@ Tooltips:
|
|||
Distraction Free Settings:
|
||||
Hide Channels: Adja meg a csatorna nevét vagy csatornaazonosítóját, hogy elrejtse
|
||||
az összes videót, lejátszási listát és magát a csatornát, hogy ne jelenjen meg
|
||||
a keresésben vagy a népszerűségben. A megadott csatornanévnek teljes egyezésnek
|
||||
kell lennie, és megkülönbözteti a kis- és nagybetűket.
|
||||
a keresésben, illetve a felkapott, legnépszerűbb és legajánlottabb. A megadott
|
||||
csatornanévnek teljes egyezésnek kell lennie, és megkülönbözteti a kis- és nagybetűket.
|
||||
Hide Subscriptions Live: Ezt a beállítást felülírja az alkalmazásszintű „{appWideSetting}”
|
||||
beállítás a(z) „{settingsSection}” „{subsection}” szakaszában
|
||||
SponsorBlock Settings:
|
||||
UseDeArrowTitles: Cserélje le a videocímeket a DeArrow által beküldött, felhasználó
|
||||
által beküldött címekre.
|
||||
|
|
|
@ -30,6 +30,11 @@ Close: 'Tutup'
|
|||
Back: 'Kembali'
|
||||
Forward: 'Maju'
|
||||
|
||||
# Global
|
||||
# Anything shared among components / views should be put here
|
||||
Global:
|
||||
Videos: 'Video'
|
||||
|
||||
Version {versionNumber} is now available! Click for more details: 'Versi {versionNumber}
|
||||
sekarang tersedia! Klik untuk detail lebih lanjut'
|
||||
Download From Site: 'Unduh dari Situs'
|
||||
|
|
|
@ -30,6 +30,13 @@ Close: 'Loka'
|
|||
Back: 'Til baka'
|
||||
Forward: 'Áfram'
|
||||
|
||||
# Global
|
||||
# Anything shared among components / views should be put here
|
||||
Global:
|
||||
Videos: 'Myndskeið'
|
||||
Shorts: Stuttmyndir
|
||||
Live: Í beinni
|
||||
|
||||
Version {versionNumber} is now available! Click for more details: 'Útgáfa {versionNumber}
|
||||
er tiltæk! Smelltu til að skoða nánar'
|
||||
Download From Site: 'Sækja af vefsvæði'
|
||||
|
@ -92,6 +99,9 @@ Subscriptions:
|
|||
Disabled Automatic Fetching: Þú hefur gert sjálfvirkt niðurhal áskrifta óvirkt.
|
||||
Endurlestu áskriftirnar og þær munu birtast hér.
|
||||
Empty Channels: Rásirnar sem þú ert með í áskrift eru er ekki með nein myndskeið.
|
||||
Subscriptions Tabs: Áskriftaflipar
|
||||
All Subscription Tabs Hidden: Allir áskriftaflipar eru faldir. Til að sjá efni hér,
|
||||
skaltu gera einhverja flipa sýnilega í "{subsection}" hlutanum í "{settingsSection}".
|
||||
More: 'Meira'
|
||||
Trending:
|
||||
Trending: 'Í umræðunni'
|
||||
|
@ -335,10 +345,16 @@ Settings:
|
|||
Channel Page: Rásasíða
|
||||
Watch Page: Áhorfssíða
|
||||
General: Almennt
|
||||
Subscriptions Page: Áskriftasíða
|
||||
Hide Channel Shorts: Fela stuttmyndir rása
|
||||
Hide Channel Playlists: Fela spilunarlista rása
|
||||
Hide Channel Community: Fela samfélag rása
|
||||
Hide Featured Channels: Fela rásir í deiglunni
|
||||
Hide Channel Podcasts: Fela hlaðvörp rása
|
||||
Hide Channel Releases: Fela útgáfur rása
|
||||
Hide Subscriptions Shorts: Fela stuttmyndir áskrifta
|
||||
Hide Subscriptions Live: Fela bein streymi áskrifta
|
||||
Hide Subscriptions Videos: Fela myndskeið áskrifta
|
||||
Data Settings:
|
||||
Data Settings: 'Stillingar gagna'
|
||||
Select Import Type: 'Veldu tegund innflutnings'
|
||||
|
@ -417,6 +433,7 @@ Settings:
|
|||
Prompt To Skip: Spyrja hvort eigi að sleppa
|
||||
Do Nothing: Gera ekkert
|
||||
Category Color: Litur flokks
|
||||
UseDeArrowTitles: Nota DeArrow myndskeiðatitla
|
||||
External Player Settings:
|
||||
Custom External Player Arguments: Sérsniðin viðföng fyrir utanaðkomandi spilara
|
||||
Custom External Player Executable: Sérsniðin skipun fyrir utanaðkomandi spilara
|
||||
|
@ -577,9 +594,16 @@ Channel:
|
|||
Community: Samfélag
|
||||
This channel currently does not have any posts: Þessi rás er ekki með neinar færslur
|
||||
Shorts:
|
||||
Shorts: Stuttmyndir
|
||||
This channel does not currently have any shorts: Þessi rás er í augnablikinu ekki
|
||||
með neinar stuttmyndir
|
||||
Releases:
|
||||
Releases: Útgáfur
|
||||
This channel does not currently have any releases: Þessi rás er ekki með neinar
|
||||
útgáfur í augnablikinu
|
||||
Podcasts:
|
||||
Podcasts: Hlaðvörp
|
||||
This channel does not currently have any podcasts: Þessi rás er ekki með nein
|
||||
hlaðvörp í augnablikinu
|
||||
Video:
|
||||
Mark As Watched: 'Merkja sem búið að horfa á'
|
||||
Remove From History: 'Fjarlægja úr áhorfsferli'
|
||||
|
@ -873,8 +897,14 @@ Tooltips:
|
|||
minnislæga skyndiminnis-diskmynd. Veldur aukinni notkun á vinnsluminni.
|
||||
Distraction Free Settings:
|
||||
Hide Channels: Settu inn heiti eða auðkenni rásar til að fela öll myndskeið, spilunarlista
|
||||
og sjálfa rásina við leit eða vinsældaskráningu. Heiti rásarinnar sem sett er
|
||||
inn þarf að vera nákvæmlega stafrétt og tekur tillit til hástafa/lágstafa.
|
||||
og sjálfa rásina við leit eða því sem er vinsælast, mest skoðað og mælt með.
|
||||
Heiti rásarinnar sem sett er inn þarf að vera nákvæmlega stafrétt og tekur tillit
|
||||
til hástafa/lágstafa.
|
||||
Hide Subscriptions Live: Þessa stillingu er hægt að taka yfir með "{appWideSetting}"
|
||||
stillingunni fyrir allt forritið, í "{subsection}" hlutanum í "{settingsSection}"
|
||||
SponsorBlock Settings:
|
||||
UseDeArrowTitles: Skipta út titlum myndskeiða fyrir titla sem notendur hafa sent
|
||||
inn á DeArrow.
|
||||
Local API Error (Click to copy): 'Villa í staðværu API-kerfisviðmóti (smella til að
|
||||
afrita)'
|
||||
Invidious API Error (Click to copy): 'Villa í Invidious API-kerfisviðmóti (smella
|
||||
|
|
|
@ -30,6 +30,13 @@ Close: 'Chiudi'
|
|||
Back: 'Indietro'
|
||||
Forward: 'Avanti'
|
||||
|
||||
# Global
|
||||
# Anything shared among components / views should be put here
|
||||
Global:
|
||||
Videos: 'Video'
|
||||
Shorts: Video brevi
|
||||
Live: Dal vivo
|
||||
|
||||
# Search Bar
|
||||
Search / Go to URL: 'Cerca o aggiungi URL YouTube'
|
||||
# In Filter Button
|
||||
|
@ -87,6 +94,9 @@ Subscriptions:
|
|||
Disabled Automatic Fetching: Hai disabilitato il recupero automatico dell'abbonamento.
|
||||
Aggiorna gli abbonamenti per vederli qui.
|
||||
Empty Channels: I canali a cui sei iscritto attualmente non hanno alcun video.
|
||||
Subscriptions Tabs: Schede iscrizioni
|
||||
All Subscription Tabs Hidden: Tutte le schede di iscrizione sono nascoste. Per vedere
|
||||
i contenuti qui, scopri le schede nella sezione "{subsection}" in "{settingsSection}".
|
||||
Trending:
|
||||
Trending: 'Tendenze'
|
||||
Music: Musica
|
||||
|
@ -417,6 +427,12 @@ Settings:
|
|||
Watch Page: Pagina di visualizzazione
|
||||
Side Bar: Barra laterale
|
||||
Channel Page: Pagina del canale
|
||||
Subscriptions Page: Pagina delle iscrizioni
|
||||
Hide Channel Podcasts: Nascondi i podcast del canale
|
||||
Hide Channel Releases: Nascondi i rilasci del canale
|
||||
Hide Subscriptions Live: Nascondi le iscrizioni dal vivo
|
||||
Hide Subscriptions Videos: Nascondi i video delle iscrizioni
|
||||
Hide Subscriptions Shorts: Nascondi le iscrizioni ai video brevi
|
||||
The app needs to restart for changes to take effect. Restart and apply change?: L'app
|
||||
deve essere riavviata affinché le modifiche abbiano effetto. Riavviare e applicare
|
||||
la modifica?
|
||||
|
@ -491,7 +507,7 @@ Settings:
|
|||
Remove Password: Rimuovi password
|
||||
About:
|
||||
#On About page
|
||||
About: 'Informazioni'
|
||||
About: 'Informazioni su'
|
||||
#& About
|
||||
'This software is FOSS and released under the GNU Affero General Public License v3.0.': 'Questo
|
||||
software è rilasciato con licenza gratuita AGPL-3.0.'
|
||||
|
@ -571,7 +587,7 @@ Channel:
|
|||
Newest: 'Più nuovi'
|
||||
Oldest: 'Più vecchi'
|
||||
About:
|
||||
About: 'Informazioni'
|
||||
About: 'Informazioni su'
|
||||
Channel Description: 'Descrizione canale'
|
||||
Featured Channels: 'Canali in evidenza'
|
||||
Tags:
|
||||
|
@ -600,9 +616,16 @@ Channel:
|
|||
This channel does not currently have any live streams: Questo canale attualmente
|
||||
non ha alcun video dal vivo
|
||||
Shorts:
|
||||
Shorts: Video brevi
|
||||
This channel does not currently have any shorts: Questo canale attualmente non
|
||||
ha video brevi
|
||||
Podcasts:
|
||||
Podcasts: Podcast
|
||||
This channel does not currently have any podcasts: Questo canale non ha attualmente
|
||||
alcun podcast
|
||||
Releases:
|
||||
Releases: Rilasci
|
||||
This channel does not currently have any releases: Questo canale non ha attualmente
|
||||
alcun rilascio
|
||||
Video:
|
||||
Mark As Watched: 'Segna come già visto'
|
||||
Remove From History: 'Rimuovi dalla cronologia'
|
||||
|
@ -629,7 +652,7 @@ Video:
|
|||
'Chat is disabled or the Live Stream has ended.': 'La chat è disabilitata o la diretta
|
||||
è terminata.'
|
||||
Live chat is enabled. Chat messages will appear here once sent.: 'La chat dal vivo
|
||||
è abilitata. I messaggi appariranno qui una volta inviati.'
|
||||
è abilitata. I messaggi appariranno qui una volta inviati.'
|
||||
'Live Chat is currently not supported with the Invidious API. A direct connection to YouTube is required.': 'La
|
||||
chat dal vivo non è attualmente supportata con le API di Invidious. È necessaria
|
||||
una connessione diretta a YouTube.'
|
||||
|
@ -965,10 +988,12 @@ Tooltips:
|
|||
una cache di immagini in memoria personalizzata. Comporta un aumento dell'utilizzo
|
||||
della RAM.
|
||||
Distraction Free Settings:
|
||||
Hide Channels: Inserisci il nome o l'ID di un canale per nascondere tutti i video,
|
||||
le playlist e il canale stesso dalla visualizzazione nelle ricerche o nelle
|
||||
tendenze. Il nome del canale inserito deve essere una corrispondenza completa
|
||||
e fa distinzione tra maiuscole e minuscole.
|
||||
Hide Channels: Inserisci il nome o l'ID di un canale per impedire che tutti i
|
||||
video, le playlist e il canale stesso vengano visualizzati nelle ricerche, tendenze,
|
||||
più popolari e consigliati. Il nome del canale inserito deve avere una corrispondenza
|
||||
completa e fa distinzione tra maiuscole e minuscole.
|
||||
Hide Subscriptions Live: Questa impostazione è sovrascritta dall'impostazione
|
||||
"{appWideSetting}" a livello di app, nella sezione "{subsection}" di "{settingsSection}"
|
||||
SponsorBlock Settings:
|
||||
UseDeArrowTitles: Sostituisci i titoli dei video con titoli inviati dagli utenti
|
||||
da DeArrow.
|
||||
|
|
|
@ -27,6 +27,13 @@ Close: '閉じる'
|
|||
Back: '戻る'
|
||||
Forward: '進む'
|
||||
|
||||
# Global
|
||||
# Anything shared among components / views should be put here
|
||||
Global:
|
||||
Videos: '動画'
|
||||
Shorts: ショート動画
|
||||
Live: ライブ配信
|
||||
|
||||
# Search Bar
|
||||
Search / Go to URL: '検索 / URL の表示'
|
||||
# In Filter Button
|
||||
|
@ -91,8 +98,7 @@ Playlists: '再生リスト'
|
|||
User Playlists:
|
||||
Your Playlists: 'あなたの再生リスト'
|
||||
Your saved videos are empty. Click on the save button on the corner of a video to have it listed here: 保存した動画はありません。一覧に表示させるには、ビデオの角にある保存ボタンをクリックします
|
||||
Playlist Message:
|
||||
このページは、完全に動作する動画リストではありません。保存またはお気に入りと設定した動画のみが表示されます。操作が完了すると、現在ここにあるすべての動画は「お気に入り」の動画リストに移動します。
|
||||
Playlist Message: このページは、完全に動作する動画リストではありません。保存またはお気に入りと設定した動画のみが表示されます。操作が完了すると、現在ここにあるすべての動画は「お気に入り」の動画リストに移動します。
|
||||
Search bar placeholder: 動画リスト内の検索
|
||||
Empty Search Message: この再生リストに、検索に一致する動画はありません
|
||||
History:
|
||||
|
@ -539,7 +545,6 @@ Channel:
|
|||
This channel does not currently have any live streams: このチャンネルは現在、ライブ配信を行っていません
|
||||
Live: ライブ配信
|
||||
Shorts:
|
||||
Shorts: ショート動画
|
||||
This channel does not currently have any shorts: このチャンネルには現在ショート動画がありません
|
||||
Video:
|
||||
Open in YouTube: 'YouTube で表示'
|
||||
|
@ -849,7 +854,7 @@ Tooltips:
|
|||
Replace HTTP Cache: Electron のディスクに基づく HTTP キャッシュを無効化し、メモリ内で独自の画像キャッシュを使用します。このことにより
|
||||
RAM の使用率は増加します。
|
||||
Distraction Free Settings:
|
||||
Hide Channels: チャンネル名またはチャンネル ID
|
||||
Hide Channels: チャンネル名またはチャンネル ID
|
||||
を入力すると、すべてのビデオ、再生リスト、およびチャンネル自体が検索や人気に表示されなくなります。入力するチャンネル名は完全に一致することが必要で、大文字と小文字を区別します。
|
||||
SponsorBlock Settings:
|
||||
UseDeArrowTitles: 動画のタイトルを DeArrow からユーザーが投稿したタイトルに置き換えます。
|
||||
|
|
|
@ -29,6 +29,11 @@ Close: '닫기'
|
|||
Back: '뒤로가기'
|
||||
Forward: '앞으로가기'
|
||||
|
||||
# Global
|
||||
# Anything shared among components / views should be put here
|
||||
Global:
|
||||
Videos: '비디오'
|
||||
|
||||
Version {versionNumber} is now available! Click for more details: '{versionNumber}
|
||||
버전이 사용가능합니다! 클릭하여 자세한 정보를 확인하세요'
|
||||
Download From Site: '사이트로부터 다운로드'
|
||||
|
|
|
@ -30,6 +30,11 @@ Back: 'Atgal'
|
|||
Forward: 'Pirmyn'
|
||||
Open New Window: 'Atidaryti naują langą'
|
||||
|
||||
# Global
|
||||
# Anything shared among components / views should be put here
|
||||
Global:
|
||||
Videos: 'Vaizdo įrašai'
|
||||
|
||||
Version {versionNumber} is now available! Click for more details: 'Versija {versionNumber}
|
||||
jau prieinama! Spustelėkite, jei norite gauti daugiau informacijos'
|
||||
Download From Site: 'Atsisiųsti iš svetainės'
|
||||
|
|
|
@ -29,6 +29,13 @@ Close: 'Lukk'
|
|||
Back: 'Tilbake'
|
||||
Forward: 'Framover'
|
||||
|
||||
# Global
|
||||
# Anything shared among components / views should be put here
|
||||
Global:
|
||||
Videos: 'Videoer'
|
||||
Shorts: Kortvideoer
|
||||
Live: Direkte
|
||||
|
||||
# Search Bar
|
||||
Search / Go to URL: 'Søk/gå til nettadresse'
|
||||
# In Filter Button
|
||||
|
@ -545,7 +552,6 @@ Channel:
|
|||
This channel currently does not have any posts: Denne kanalen har ingen oppføringer
|
||||
Shorts:
|
||||
This channel does not currently have any shorts: Denne kanalen har ingen kortvideoer
|
||||
Shorts: Kortvideoer
|
||||
Live:
|
||||
Live: Direkte
|
||||
This channel does not currently have any live streams: Denne kanalen har ikke
|
||||
|
|
|
@ -30,6 +30,11 @@ Close: 'Sluiten'
|
|||
Back: 'Ga terug'
|
||||
Forward: 'Ga vooruit'
|
||||
|
||||
# Global
|
||||
# Anything shared among components / views should be put here
|
||||
Global:
|
||||
Videos: 'Video''s'
|
||||
|
||||
# Search Bar
|
||||
Search / Go to URL: 'Zoeken / Ga naar URL'
|
||||
# In Filter Button
|
||||
|
|
|
@ -30,6 +30,11 @@ Close: 'Lukk'
|
|||
Back: 'Tilbake'
|
||||
Forward: 'Framover'
|
||||
|
||||
# Global
|
||||
# Anything shared among components / views should be put here
|
||||
Global:
|
||||
Videos: 'Videoar'
|
||||
|
||||
Version {versionNumber} is now available! Click for more details: 'Versjon {versionNumber}
|
||||
er no tilgjengeleg! Klikk for meir informasjon'
|
||||
Download From Site: 'Last ned frå nettstaden'
|
||||
|
|
|
@ -28,6 +28,13 @@ Close: 'Zamknij'
|
|||
Back: 'Wstecz'
|
||||
Forward: 'Naprzód'
|
||||
|
||||
# Global
|
||||
# Anything shared among components / views should be put here
|
||||
Global:
|
||||
Videos: 'Filmy'
|
||||
Shorts: Filmy Short
|
||||
Live: Transmisje
|
||||
|
||||
# Search Bar
|
||||
Search / Go to URL: 'Szukaj / Przejdź do adresu URL'
|
||||
# In Filter Button
|
||||
|
@ -84,6 +91,9 @@ Subscriptions:
|
|||
Disabled Automatic Fetching: Wyłączyłeś automatyczne pobieranie subskrypcji. Odśwież
|
||||
subskrypcje, by je zobaczyć.
|
||||
Empty Channels: Twoje subskrypcje nie mają obecnie żadnych filmów.
|
||||
All Subscription Tabs Hidden: Wszystkie karty subskrypcji są pochowane. Aby je zobaczyć,
|
||||
proszę odznaczyć ich ukrycie w podgrupie „{subsection}” grupy „{settingsSection}”.
|
||||
Subscriptions Tabs: Karty subskrypcji
|
||||
Trending:
|
||||
Trending: 'Na czasie'
|
||||
Trending Tabs: Karty „Na czasie”
|
||||
|
@ -415,6 +425,12 @@ Settings:
|
|||
Channel Page: Strona kanału
|
||||
General: Ogólne
|
||||
Watch Page: Strona odtwarzacza
|
||||
Subscriptions Page: Strona subskrypcji
|
||||
Hide Channel Releases: Schowaj wydawnictwa kanału
|
||||
Hide Channel Podcasts: Schowaj podkasty kanału
|
||||
Hide Subscriptions Videos: Schowaj filmy z subskrypcji
|
||||
Hide Subscriptions Shorts: Schowaj filmy Short z subskrypcji
|
||||
Hide Subscriptions Live: Schowaj transmisje live z subskrypcji
|
||||
The app needs to restart for changes to take effect. Restart and apply change?: Aplikacja
|
||||
musi zostać ponownie uruchomiona, aby zmiany zostały wprowadzone. Uruchomić ponownie
|
||||
i zastosować zmiany?
|
||||
|
@ -448,6 +464,7 @@ Settings:
|
|||
Prompt To Skip: Zapytaj, czy pominąć
|
||||
Do Nothing: Nic nie rób
|
||||
Category Color: Kolor segmentu
|
||||
UseDeArrowTitles: Użyj tytułów filmów z DeArrow
|
||||
External Player Settings:
|
||||
Custom External Player Arguments: Niestandardowe argumenty zewnętrznego odtwarzacza
|
||||
Custom External Player Executable: Niestandardowy plik wykonywalny zewnętrznego
|
||||
|
@ -603,9 +620,15 @@ Channel:
|
|||
This channel does not currently have any live streams: Ten kanał nie ma obecnie
|
||||
żadnych transmisji
|
||||
Shorts:
|
||||
Shorts: Filmy Short
|
||||
This channel does not currently have any shorts: Ten kanał nie ma obecnie żadnych
|
||||
filmów Short
|
||||
Releases:
|
||||
Releases: Wydania
|
||||
This channel does not currently have any releases: Ten kanał nie ma obecnie żadnych
|
||||
wydawnictw
|
||||
Podcasts:
|
||||
Podcasts: Podkasty
|
||||
This channel does not currently have any podcasts: Ten kanał nie ma żadnych podkastów
|
||||
Video:
|
||||
Mark As Watched: 'Oznacz jako obejrzany'
|
||||
Remove From History: 'Usuń z historii'
|
||||
|
@ -676,7 +699,7 @@ Video:
|
|||
Reverse Playlist: Odwróć playlistę
|
||||
Shuffle Playlist: Losuj z playlisty
|
||||
Loop Playlist: Zapętl playlistę
|
||||
Starting soon, please refresh the page to check again: Wkrótce się zacznie, proszę
|
||||
Starting soon, please refresh the page to check again: Wkrótce się zacznie. Proszę
|
||||
odświeżyć stronę, aby ponownie sprawdzić
|
||||
Audio:
|
||||
Best: Najlepsza
|
||||
|
@ -965,8 +988,14 @@ Tooltips:
|
|||
to większe użycie pamięci RAM.
|
||||
Distraction Free Settings:
|
||||
Hide Channels: Wprowadź nazwę albo ID kanału, aby schować wszystkie filmy i playlisty
|
||||
tego kanału, oraz sam kanał z wyszukiwań, oraz z zakładki „Na czasie”. Nazwa
|
||||
kanału musi być dokładnym dopasowaniem, z uwzględnieniem wielkości liter.
|
||||
tego kanału, oraz sam kanał z wyszukiwań, z zakładek „Na czasie” i „Popularne”
|
||||
oraz z polecanych. Nazwa kanału musi być dokładnym dopasowaniem, z uwzględnieniem
|
||||
wielkości liter.
|
||||
Hide Subscriptions Live: Ta opcja została nadpisana opcją ogólną „{appWideSetting}”
|
||||
z podgrupy „{subsection}” grupy „{settingsSection}”
|
||||
SponsorBlock Settings:
|
||||
UseDeArrowTitles: Zastąp tytuły filmów tytułami zasugerowanymi przez użytkowników
|
||||
DeArrow.
|
||||
Playing Next Video Interval: Odtwarzanie kolejnego filmu już za chwilę. Wciśnij aby
|
||||
przerwać. | Odtwarzanie kolejnego filmu za {nextVideoInterval} sekundę. Wciśnij
|
||||
aby przerwać. | Odtwarzanie kolejnego filmu za {nextVideoInterval} sekund. Wciśnij
|
||||
|
|
|
@ -28,6 +28,13 @@ Close: 'Fechar'
|
|||
Back: 'Voltar'
|
||||
Forward: 'Avançar'
|
||||
|
||||
# Global
|
||||
# Anything shared among components / views should be put here
|
||||
Global:
|
||||
Videos: 'Vídeos'
|
||||
Shorts: Shorts
|
||||
Live: Ao vivo
|
||||
|
||||
# Search Bar
|
||||
Search / Go to URL: 'Buscar/Ir ao URL'
|
||||
# In Filter Button
|
||||
|
@ -447,6 +454,7 @@ Settings:
|
|||
Auto Skip: Pular automaticamente
|
||||
Skip Option: Opção de pular
|
||||
Category Color: Cor da categoria
|
||||
UseDeArrowTitles: Utilizar títulos de vídeo DeArrow
|
||||
External Player Settings:
|
||||
Custom External Player Arguments: Argumentos de player externo personalizados
|
||||
External Player: Player externo
|
||||
|
@ -592,7 +600,6 @@ Channel:
|
|||
This channel does not currently have any live streams: Este canal não tem nenhuma
|
||||
transmissão ao vivo no momento
|
||||
Shorts:
|
||||
Shorts: Shorts
|
||||
This channel does not currently have any shorts: Este canal não tem atualmente
|
||||
nenhum short
|
||||
Video:
|
||||
|
@ -958,9 +965,13 @@ Tooltips:
|
|||
Replace HTTP Cache: Desabilita o cache HTTP baseado em disco do Electron e habilita
|
||||
um cache de imagem em memória personalizado. Levará ao aumento do uso de RAM.
|
||||
Distraction Free Settings:
|
||||
Hide Channels: Digite um nome ou ID de canal para ocultar todos os vídeos, listas
|
||||
de reprodução e o próprio canal dos resultados da busca ou nas tendências. O
|
||||
nome do canal digitado deve coincidir exatamente, observando maiúsculas e minúsculas.
|
||||
Hide Channels: Digite um nome ou ID de canal para ocultar todos os vídeos, as
|
||||
listas de reprodução e o próprio canal dos resultados da busca, tendências,
|
||||
mais populares e recomendados. O nome do canal digitado deve coincidir exatamente,
|
||||
observando maiúsculas e minúsculas.
|
||||
SponsorBlock Settings:
|
||||
UseDeArrowTitles: Substituir títulos de vídeo por títulos enviados pelo usuário
|
||||
a partir do DeArrow.
|
||||
More: Mais
|
||||
Playing Next Video Interval: Reproduzindo o próximo vídeo imediatamente. Clique para
|
||||
cancelar. | Reproduzindo o próximo vídeo em {nextVideoInterval} segundo(s). Clique
|
||||
|
|
|
@ -30,6 +30,13 @@ Close: Fechar
|
|||
Back: Recuar
|
||||
Forward: Avançar
|
||||
|
||||
# Global
|
||||
# Anything shared among components / views should be put here
|
||||
Global:
|
||||
Videos: Vídeos
|
||||
Shorts: Curtas
|
||||
Live: Em directo
|
||||
|
||||
Version {versionNumber} is now available! Click for more details: A versão {versionNumber}
|
||||
já está disponível! Clique para mais detalhes
|
||||
Download From Site: Descarregar do site
|
||||
|
@ -341,6 +348,8 @@ Settings:
|
|||
Channel Page: Página do canal
|
||||
Watch Page: Ver página
|
||||
General: Geral
|
||||
Hide Channel Releases: Ocultar as libertações do canal
|
||||
Hide Channel Podcasts: Ocultar podcasts do canal
|
||||
Data Settings:
|
||||
Data Settings: Definições de dados
|
||||
Select Import Type: Escolher tipo de importação
|
||||
|
@ -420,6 +429,7 @@ Settings:
|
|||
Prompt To Skip: Perguntar se quero ignorar
|
||||
Do Nothing: Nada fazer
|
||||
Category Color: Cor da categoria
|
||||
UseDeArrowTitles: Utilizar títulos de vídeo DeArrow
|
||||
External Player Settings:
|
||||
Custom External Player Arguments: Argumentos do reprodutor externo personalizado
|
||||
Custom External Player Executable: Executável de reprodutor externo personalizado
|
||||
|
@ -584,9 +594,16 @@ Channel:
|
|||
This channel does not currently have any live streams: Este canal não tem atualmente
|
||||
nenhuma transmissão ao vivo
|
||||
Shorts:
|
||||
Shorts: Curtas
|
||||
This channel does not currently have any shorts: Este canal não tem atualmente
|
||||
nenhum canal curto
|
||||
Releases:
|
||||
Releases: Lançamentos
|
||||
This channel does not currently have any releases: Este canal não tem atualmente
|
||||
nenhum lançamento
|
||||
Podcasts:
|
||||
This channel does not currently have any podcasts: Este canal não tem atualmente
|
||||
podcasts
|
||||
Podcasts: Podcasts
|
||||
Video:
|
||||
Mark As Watched: Marcar como visto
|
||||
Remove From History: Remover do histórico
|
||||
|
@ -869,10 +886,13 @@ Tooltips:
|
|||
Replace HTTP Cache: Desativa a cache HTTP Electron e ativa uma cache de imagem
|
||||
na memória personalizada. Levará ao aumento da utilização de RAM.
|
||||
Distraction Free Settings:
|
||||
Hide Channels: Introduza o nome de um canal ou ID do canal para esconder todos
|
||||
os vídeos, listas de reprodução e o próprio canal de aparecer em busca ou em
|
||||
tendências. O nome do canal introduzido tem de ser uma correspondência completa
|
||||
e é sensível a maiúsculas e minúsculas.
|
||||
Hide Channels: Introduza um nome de canal ou um ID de canal para ocultar todos
|
||||
os vídeos, listas de reprodução e o próprio canal de aparecerem na pesquisa,
|
||||
tendências, mais populares e recomendados. O nome do canal introduzido tem de
|
||||
corresponder na totalidade e é sensível a maiúsculas e minúsculas.
|
||||
SponsorBlock Settings:
|
||||
UseDeArrowTitles: Substituir títulos de vídeo por títulos enviados pelo utilizador
|
||||
a partir do DeArrow.
|
||||
Local API Error (Click to copy): API local encontrou um erro (clique para copiar)
|
||||
Invidious API Error (Click to copy): API Invidious encontrou um erro (clique para
|
||||
copiar)
|
||||
|
|
|
@ -29,6 +29,13 @@ Close: 'Fechar'
|
|||
Back: 'Recuar'
|
||||
Forward: 'Avançar'
|
||||
|
||||
# Global
|
||||
# Anything shared among components / views should be put here
|
||||
Global:
|
||||
Videos: 'Vídeos'
|
||||
Shorts: Curtas
|
||||
Live: Em directo
|
||||
|
||||
Version {versionNumber} is now available! Click for more details: 'A versão {versionNumber}
|
||||
está disponível! Clique aqui para mais informações'
|
||||
Download From Site: 'Descarregar do site'
|
||||
|
@ -89,6 +96,10 @@ Subscriptions:
|
|||
Empty Channels: Os canais subscritos não têm, atualmente, quaisquer vídeos.
|
||||
Disabled Automatic Fetching: Desativou a atualização automática de subscrições.
|
||||
Atualize as subscrições para as ver aqui.
|
||||
Subscriptions Tabs: Separadores de subscrições
|
||||
All Subscription Tabs Hidden: Todos os separadores de subscrição estão ocultos.
|
||||
Para ver o conteúdo aqui, desoculte alguns separadores na secção "{subsection}"
|
||||
em "{settingsSection}".
|
||||
Trending:
|
||||
Trending: 'Tendências'
|
||||
Trending Tabs: Separador de tendências
|
||||
|
@ -391,6 +402,7 @@ Settings:
|
|||
Prompt To Skip: Perguntar se quero ignorar
|
||||
Do Nothing: Nada fazer
|
||||
Category Color: Cor da categoria
|
||||
UseDeArrowTitles: Utilizar títulos de vídeo DeArrow
|
||||
Proxy Settings:
|
||||
Error getting network information. Is your proxy configured properly?: Erro ao
|
||||
obter informações da rede. O seu proxy está configurado corretamente?
|
||||
|
@ -438,6 +450,12 @@ Settings:
|
|||
Channel Page: Página do canal
|
||||
Watch Page: Ver página
|
||||
General: Geral
|
||||
Subscriptions Page: Página de subscrições
|
||||
Hide Channel Podcasts: Ocultar podcasts do canal
|
||||
Hide Channel Releases: Ocultar as libertações do canal
|
||||
Hide Subscriptions Videos: Ocultar subscrições de vídeos
|
||||
Hide Subscriptions Shorts: Ocultar subscrições de vídeos curtos
|
||||
Hide Subscriptions Live: Ocultar subscrições de vídeos em direto
|
||||
External Player Settings:
|
||||
Custom External Player Arguments: Argumentos do reprodutor externo personalizado
|
||||
Custom External Player Executable: Executável de reprodutor externo personalizado
|
||||
|
@ -631,9 +649,16 @@ Channel:
|
|||
This channel does not currently have any live streams: Este canal não tem atualmente
|
||||
nenhuma transmissão ao vivo
|
||||
Shorts:
|
||||
Shorts: Curtas
|
||||
This channel does not currently have any shorts: Este canal não tem atualmente
|
||||
nenhum canal curto
|
||||
Releases:
|
||||
Releases: Lançamentos
|
||||
This channel does not currently have any releases: Este canal não tem atualmente
|
||||
nenhum lançamento
|
||||
Podcasts:
|
||||
Podcasts: Podcasts
|
||||
This channel does not currently have any podcasts: Este canal não tem atualmente
|
||||
podcasts
|
||||
Video:
|
||||
Mark As Watched: 'Marcar como visto'
|
||||
Remove From History: 'Remover do histórico'
|
||||
|
@ -958,10 +983,15 @@ Tooltips:
|
|||
Replace HTTP Cache: Desativa a cache HTTP Electron e ativa uma cache de imagem
|
||||
na memória personalizada. Levará ao aumento da utilização de RAM.
|
||||
Distraction Free Settings:
|
||||
Hide Channels: Digite um nome ou ID do canal para ocultar todos os vídeos, listas
|
||||
de reprodução e o próprio canal, de forma a não aparecer em pesquisas ou tendências.
|
||||
O nome do canal inserido deve ser exatamente igual ao nome do canal e é sensível
|
||||
a maiúsculas e minúsculas.
|
||||
Hide Channels: Introduza um nome de canal ou um ID de canal para ocultar todos
|
||||
os vídeos, listas de reprodução e o próprio canal de aparecerem na pesquisa,
|
||||
tendências, mais populares e recomendados. O nome do canal introduzido tem de
|
||||
corresponder na totalidade e é sensível a maiúsculas e minúsculas.
|
||||
Hide Subscriptions Live: Esta definição é substituída pela definição de toda a
|
||||
aplicação "{appWideSetting}", na secção "{subsection}" da "{settingsSection}"
|
||||
SponsorBlock Settings:
|
||||
UseDeArrowTitles: Substituir títulos de vídeo por títulos enviados pelo utilizador
|
||||
a partir do DeArrow.
|
||||
Search Bar:
|
||||
Clear Input: Limpar entrada
|
||||
Are you sure you want to open this link?: Tem a certeza de que deseja abrir a ligação?
|
||||
|
|
|
@ -29,6 +29,13 @@ Close: 'Închideți'
|
|||
Back: 'Înapoi'
|
||||
Forward: 'Înainte'
|
||||
|
||||
# Global
|
||||
# Anything shared among components / views should be put here
|
||||
Global:
|
||||
Videos: 'Videoclipuri'
|
||||
|
||||
Shorts: Shorts
|
||||
Live: Live
|
||||
Version {versionNumber} is now available! Click for more details: 'Versiunea {versionNumber}
|
||||
este acum disponibilă! Click pentru mai multe detalii'
|
||||
Download From Site: 'Descărcați de pe site'
|
||||
|
@ -60,12 +67,14 @@ Search Filters:
|
|||
Videos: 'Videoclipuri'
|
||||
Channels: 'Canale'
|
||||
#& Playlists
|
||||
Movies: Filme
|
||||
Duration:
|
||||
Duration: 'Durata'
|
||||
All Durations: 'Toate duratele'
|
||||
Short (< 4 minutes): 'Scurt (< 4 minute)'
|
||||
Long (> 20 minutes): 'Lung (> 20 minute)'
|
||||
# On Search Page
|
||||
Medium (4 - 20 minutes): Mediu (4 - 20 minute)
|
||||
Search Results: 'Rezultatele căutării'
|
||||
Fetching results. Please wait: 'Se obțin rezultatele. Vă rugăm să așteptați'
|
||||
Fetch more results: 'Obțineți mai multe rezultate'
|
||||
|
@ -89,6 +98,7 @@ Subscriptions:
|
|||
Empty Channels: Canalele la care sunteți abonat(ă) nu au în prezent clipuri video.
|
||||
Disabled Automatic Fetching: Ai dezactivat obținerea automată de abonamente. Reîmprospătează
|
||||
abonamentele pentru a le vedea aici.
|
||||
Subscriptions Tabs: Filele Abonamente
|
||||
Trending:
|
||||
Trending: 'Tendințe'
|
||||
Trending Tabs: File în tendințe
|
||||
|
@ -216,6 +226,7 @@ Settings:
|
|||
Disable Smooth Scrolling: Dezactivați derularea lină
|
||||
Expand Side Bar by Default: Extindeți bara laterală în mod implicit
|
||||
Hide Side Bar Labels: Ascunde etichetele din bara laterală
|
||||
Hide FreeTube Header Logo: Ascundeți logo-ul FreeTube Header
|
||||
Player Settings:
|
||||
Player Settings: 'Setări player'
|
||||
Force Local Backend for Legacy Formats: 'Forțați backend-ul local pentru formatele
|
||||
|
@ -269,6 +280,11 @@ Settings:
|
|||
Max Video Playback Rate: Viteza maximă de redare
|
||||
Scroll Playback Rate Over Video Player: Schimbă viteza de redare cu ajutorul rotiței
|
||||
de scroll
|
||||
Enter Fullscreen on Display Rotate: Intrați pe ecran complet pe afișaj Rotire
|
||||
Comment Auto Load:
|
||||
Comment Auto Load: Comentariu Încărcare automată
|
||||
Skip by Scrolling Over Video Player: Omiteți derulând peste player-ul video
|
||||
Allow DASH AV1 formats: Permiteți formatele DASH AV1
|
||||
Privacy Settings:
|
||||
Privacy Settings: 'Setări de confidențialitate'
|
||||
Remember History: 'Reține istoric'
|
||||
|
@ -410,6 +426,17 @@ Settings:
|
|||
Parental Control Settings: Setările controlului parental
|
||||
Show Family Friendly Only: Arata numai conținut family friendly
|
||||
Hide Search Bar: Ascunde bara de căutare
|
||||
Password Dialog:
|
||||
Password Incorrect: Parolă incorectă
|
||||
Unlock: Deblocați
|
||||
Password: Parolă
|
||||
Enter Password To Unlock: Introduceți parola pentru a debloca setările
|
||||
Password Settings:
|
||||
Remove Password: Eliminați parola
|
||||
Password Settings: Setări parolă
|
||||
Set Password To Prevent Access: Setați o parolă pentru a împiedica accesul la
|
||||
setări
|
||||
Set Password: Setați parola
|
||||
About:
|
||||
#On About page
|
||||
About: 'Despre'
|
||||
|
@ -518,6 +545,16 @@ Channel:
|
|||
About: 'Despre'
|
||||
Channel Description: 'Descrierea canalului'
|
||||
Featured Channels: 'Canale recomandate'
|
||||
Joined: S-a alăturat pe
|
||||
Location: Locație
|
||||
Community:
|
||||
Community: Comunitate
|
||||
This channel currently does not have any posts: Acest canal nu are momentan nicio
|
||||
postare
|
||||
This channel is age-restricted and currently cannot be viewed in FreeTube.: Acest
|
||||
canal are restricții de vârstă și momentan nu poate fi vizionat în FreeTube.
|
||||
This channel does not exist: Acest canal nu există
|
||||
This channel does not allow searching: Acest canal nu permite căutarea
|
||||
Video:
|
||||
Mark As Watched: 'Marcați ca vizionat'
|
||||
Remove From History: 'Eliminați din istoric'
|
||||
|
@ -586,6 +623,7 @@ Video:
|
|||
Ago: 'În urmă'
|
||||
Upcoming: 'În premieră la'
|
||||
Less than a minute: Mai putin de un minut
|
||||
In less than a minute: În mai puțin de un minut
|
||||
Published on: 'Publicat pe'
|
||||
Publicationtemplate: 'acum {number} {unit}'
|
||||
#& Videos
|
||||
|
@ -655,6 +693,7 @@ Video:
|
|||
Mimetype: Mimetype
|
||||
Premieres on: Premieră pe
|
||||
Premieres in: Are premiera în
|
||||
Scroll to Bottom: Derulați până jos
|
||||
Videos:
|
||||
#& Sort By
|
||||
Sort By:
|
||||
|
@ -701,6 +740,7 @@ Share:
|
|||
YouTube Channel URL copied to clipboard: URL-ul canalului YouTube copiat în clipboard
|
||||
Invidious Channel URL copied to clipboard: URL-ul Invidious al canalului a fost
|
||||
copiat în clipboard
|
||||
Share Channel: Distribuie canalul
|
||||
Mini Player: 'Mini Player'
|
||||
Comments:
|
||||
Comments: 'Comentarii'
|
||||
|
@ -810,9 +850,9 @@ Tooltips:
|
|||
a reveni rapid la rata de redare implicită (1x, cu excepția cazului în care
|
||||
aceasta a fost modificată în setări).
|
||||
General Settings:
|
||||
External Link Handling: "Alegeți comportamentul implicit atunci când se face clic\
|
||||
\ pe un link care nu poate fi deschis în FreeTube.\nÎn mod implicit, FreeTube\
|
||||
\ va deschide link-ul pe care s-a făcut clic în browserul dvs. implicit.\n"
|
||||
External Link Handling: "Alegeți comportamentul implicit atunci când se face clic
|
||||
pe un link care nu poate fi deschis în FreeTube.\nÎn mod implicit, FreeTube
|
||||
va deschide link-ul pe care s-a făcut clic în browserul dvs. implicit.\n"
|
||||
Region for Trending: Regiunea de tendințe vă permite să alegeți ce videoclipuri
|
||||
în tendințe din fiecare țară doriți să fie afișate.
|
||||
Invidious Instance: Instanța Invidious la care FreeTube se va conecta pentru apelurile
|
||||
|
@ -848,3 +888,14 @@ Channels:
|
|||
Unsubscribe Prompt: Ești sigur că dorești să te dezabonezi de la "{channelName}"?
|
||||
Screenshot Success: Capturi de ecran salvate ca "{filePath}"
|
||||
Screenshot Error: Captura de ecran a eșuat
|
||||
Preferences: Preferințe
|
||||
Chapters:
|
||||
Chapters: Capitole
|
||||
'Chapters list visible, current chapter: {chapterName}': 'Lista de capitole vizibilă,
|
||||
capitolul curent: {chapterName}'
|
||||
'Chapters list hidden, current chapter: {chapterName}': 'Lista de capitole ascunsă,
|
||||
capitolul curent: {chapterName}'
|
||||
Clipboard:
|
||||
Cannot access clipboard without a secure connection: Nu se poate accesa clipboard-ul
|
||||
fără o conexiune securizată
|
||||
Copy failed: Copierea în clipboard a eșuat
|
||||
|
|
|
@ -27,6 +27,13 @@ Close: 'Закрыть'
|
|||
Back: 'Назад'
|
||||
Forward: 'Вперёд'
|
||||
|
||||
# Global
|
||||
# Anything shared among components / views should be put here
|
||||
Global:
|
||||
Videos: 'Видео'
|
||||
Shorts: Короткие видео
|
||||
Live: Трансляции
|
||||
|
||||
# Search Bar
|
||||
Search / Go to URL: 'Поиск / Перейти по адресу'
|
||||
# In Filter Button
|
||||
|
@ -83,6 +90,9 @@ Subscriptions:
|
|||
Disabled Automatic Fetching: Вы отключили автоматическое получение подписок. Обновите
|
||||
подписки, чтобы отобразить результат.
|
||||
Empty Channels: Ваши подписанные каналы в настоящее время не содержат видео.
|
||||
Subscriptions Tabs: Вкладки подписок
|
||||
All Subscription Tabs Hidden: Все вкладки подписок скрыты. Чтобы увидеть содержимое
|
||||
здесь, пожалуйста, раскройте некоторые вкладки в разделе «{subsection}» в «{settingsSection}».
|
||||
Trending:
|
||||
Trending: 'Тренды'
|
||||
Trending Tabs: Тренды
|
||||
|
@ -409,6 +419,12 @@ Settings:
|
|||
Channel Page: Страница канала
|
||||
Watch Page: Страница просмотра
|
||||
General: Основные
|
||||
Subscriptions Page: Страница подписок
|
||||
Hide Channel Releases: Скрыть выпуски канала
|
||||
Hide Subscriptions Videos: Скрыть видео из подписок
|
||||
Hide Subscriptions Live: Скрыть трансляции из подписок
|
||||
Hide Channel Podcasts: Скрыть звукопередачи канала
|
||||
Hide Subscriptions Shorts: Скрыть короткие видео из подписок
|
||||
The app needs to restart for changes to take effect. Restart and apply change?: Чтобы
|
||||
изменения вступили в силу, необходимо перезапустить приложение. Перезапустить
|
||||
и применить изменения?
|
||||
|
@ -441,6 +457,7 @@ Settings:
|
|||
Show In Seek Bar: Показать сегмент
|
||||
Do Nothing: Ничего не делать
|
||||
Category Color: Цвет категории
|
||||
UseDeArrowTitles: Использовать заголовки видео «DeArrow»
|
||||
External Player Settings:
|
||||
Custom External Player Arguments: Аргументы внешнего проигрывателя
|
||||
Custom External Player Executable: Исполняемый файл внешнего проигрывателя
|
||||
|
@ -588,9 +605,16 @@ Channel:
|
|||
This channel does not currently have any live streams: На этом канале в настоящее
|
||||
время нет прямых трансляций
|
||||
Shorts:
|
||||
Shorts: Короткие видео
|
||||
This channel does not currently have any shorts: На этом канале пока что нет коротких
|
||||
видео
|
||||
Podcasts:
|
||||
Podcasts: Звукопередачи
|
||||
This channel does not currently have any podcasts: На этом канале пока нет ни
|
||||
одной звукопередачи
|
||||
Releases:
|
||||
Releases: Выпуски
|
||||
This channel does not currently have any releases: На этом канале пока нет ни
|
||||
одного выпуска
|
||||
Video:
|
||||
Mark As Watched: 'Отметить как просмотренное'
|
||||
Remove From History: 'Удалить из истории'
|
||||
|
@ -954,8 +978,14 @@ Tooltips:
|
|||
кэш изображений в памяти. Приведёт к увеличению использования оперативной памяти.
|
||||
Distraction Free Settings:
|
||||
Hide Channels: Введите название канала или его идентификатор, чтобы скрыть все
|
||||
видео, подборки и сам канал от показа в поиске или трендах. Введённое название
|
||||
канала должно полностью совпадать и учитывать регистр.
|
||||
видео, подборки и сам канал от показа в поиске, трендах, наиболее просматриваемых
|
||||
и желательных. Введённое название канала должно полностью совпадать и учитывать
|
||||
регистр.
|
||||
Hide Subscriptions Live: Эта настройка переопределена общей настройкой «{appWideSetting}»,
|
||||
в подразделе «{subsection}» раздела «{settingsSection}»
|
||||
SponsorBlock Settings:
|
||||
UseDeArrowTitles: Заменить пользовательски-размещённые заголовки на заголовки,
|
||||
предоставляемые «DeArrow».
|
||||
More: Больше
|
||||
Playing Next Video Interval: Воспроизведение следующего видео без задержки. Нажмите
|
||||
для отмены. | Воспроизведение следующего видео через {nextVideoInterval} сек. Нажмите
|
||||
|
|
|
@ -28,6 +28,11 @@ Close: 'Zavrieť'
|
|||
Back: 'Späť'
|
||||
Forward: 'Vpred'
|
||||
|
||||
# Global
|
||||
# Anything shared among components / views should be put here
|
||||
Global:
|
||||
Videos: 'Videá'
|
||||
|
||||
# Search Bar
|
||||
Search / Go to URL: 'Hľadať / Ísť na adresu URL'
|
||||
# In Filter Button
|
||||
|
|
|
@ -30,6 +30,11 @@ Close: 'Zapri'
|
|||
Back: 'Nazaj'
|
||||
Forward: 'Naprej'
|
||||
|
||||
# Global
|
||||
# Anything shared among components / views should be put here
|
||||
Global:
|
||||
Videos: 'Videoposnetki'
|
||||
|
||||
Version {versionNumber} is now available! Click for more details: 'Na voljo je različica
|
||||
{versionNumber}!· Za več podrobnosti kliknite tukaj'
|
||||
Download From Site: 'Prenesi iz spletne strani'
|
||||
|
|
|
@ -29,6 +29,11 @@ Close: 'Затвори'
|
|||
Back: 'Назад'
|
||||
Forward: 'Напред'
|
||||
|
||||
# Global
|
||||
# Anything shared among components / views should be put here
|
||||
Global:
|
||||
Videos: 'Видео'
|
||||
|
||||
Version {versionNumber} is now available! Click for more details: 'Верзија {versionNumber}
|
||||
је сада достуна! Кликните за више детаља'
|
||||
Download From Site: 'Преузми са сајта'
|
||||
|
|
|
@ -29,6 +29,13 @@ Close: 'Stäng'
|
|||
Back: 'Tillbaka'
|
||||
Forward: 'Framåt'
|
||||
|
||||
# Global
|
||||
# Anything shared among components / views should be put here
|
||||
Global:
|
||||
Videos: 'Videor'
|
||||
Shorts: Shorts
|
||||
Live: Live
|
||||
|
||||
Version {versionNumber} is now available! Click for more details: 'Versionen {versionNumber}
|
||||
är nu tillgänglig! Klicka för mer detaljer'
|
||||
Download From Site: 'Ladda ner från sajten'
|
||||
|
@ -272,6 +279,8 @@ Settings:
|
|||
Allow DASH AV1 formats: Tillåt DASH AV1-format
|
||||
Scroll Playback Rate Over Video Player: Skrolla uppspelningshastighet över videospelaren
|
||||
Enter Fullscreen on Display Rotate: Fullskärm vid skärmrotation
|
||||
Comment Auto Load:
|
||||
Comment Auto Load: Autoladda kommenterer
|
||||
Privacy Settings:
|
||||
Privacy Settings: 'Integritetsinställningar'
|
||||
Remember History: 'Aktivera visningshistorik'
|
||||
|
@ -390,6 +399,15 @@ Settings:
|
|||
Hide Upcoming Premieres: Dölj permiärer
|
||||
Display Titles Without Excessive Capitalisation: Visa titlar utan överdriven versalisering
|
||||
Hide Channels Placeholder: Kanalnamn eller ID
|
||||
Hide Featured Channels: Dölj Utvalda kanaler
|
||||
Hide Channel Shorts: Dölj Kanal Shorts
|
||||
Sections:
|
||||
Side Bar: Sidofält
|
||||
General: Allmänt
|
||||
Channel Page: Kanal sida
|
||||
Watch Page: Titta sida
|
||||
Hide Channel Community: Dölj Kanal gemenskap
|
||||
Hide Channel Playlists: Dölj Kanal spellistor
|
||||
The app needs to restart for changes to take effect. Restart and apply change?: Starta
|
||||
om FreeTube nu för att tillämpa ändringarna?
|
||||
Proxy Settings:
|
||||
|
@ -419,7 +437,9 @@ Settings:
|
|||
Show In Seek Bar: Visa i tidslinjen
|
||||
Do Nothing: Gör ingenting
|
||||
Auto Skip: Hoppa över automatiskt
|
||||
Prompt To Skip: Prompt för att hoppa över
|
||||
Category Color: Kategorifärg
|
||||
UseDeArrowTitles: Använd DeArrow-videotitlar
|
||||
External Player Settings:
|
||||
Ignore Unsupported Action Warnings: Ignorera händelsevarningar som inte stöds
|
||||
External Player: Extern spelare
|
||||
|
@ -604,6 +624,13 @@ Channel:
|
|||
Community: Gemenskap
|
||||
This channel currently does not have any posts: Denna kanal har för närvarande
|
||||
inga inlägg
|
||||
Shorts:
|
||||
This channel does not currently have any shorts: Den här kanalen har för närvarande
|
||||
inga shorts
|
||||
Live:
|
||||
Live: Live
|
||||
This channel does not currently have any live streams: Den här kanalen har för
|
||||
närvarande inga liveströmmar
|
||||
Video:
|
||||
Mark As Watched: 'Markera som sedd'
|
||||
Remove From History: 'Ta bort från historik'
|
||||
|
@ -637,8 +664,8 @@ Video:
|
|||
inte i denna version.'
|
||||
'Chat is disabled or the Live Stream has ended.': 'Chatten är inaktiverad eller
|
||||
så har Live strömmen avslutats.'
|
||||
Live chat is enabled. Chat messages will appear here once sent.: 'Livechatt är
|
||||
aktiverat. Meddelanden visas här när de har skickats.'
|
||||
Live chat is enabled. Chat messages will appear here once sent.: 'Livechatt är aktiverat.
|
||||
Meddelanden visas här när de har skickats.'
|
||||
'Live Chat is currently not supported with the Invidious API. A direct connection to YouTube is required.': 'Livechatt
|
||||
stöds för närvarande inte med Invidious API. En direktanslutning till YouTube
|
||||
krävs.'
|
||||
|
@ -736,6 +763,8 @@ Video:
|
|||
Upcoming: Kommande
|
||||
Show Super Chat Comment: Visa Super Chat-kommentarer
|
||||
Scroll to Bottom: Skrolla till botten
|
||||
'Live Chat is unavailable for this stream. It may have been disabled by the uploader.': Livechatt
|
||||
är inte tillgängligt för den här strömmen. Den kan ha inaktiverats av uppladdaren.
|
||||
Videos:
|
||||
#& Sort By
|
||||
Sort By:
|
||||
|
@ -874,9 +903,9 @@ Tooltips:
|
|||
Preferred API Backend: Välj den resurs som FreeTube använder för att få fram data.
|
||||
Det lokala API:et är en inbyggd utdragare. Invidious API kräver en Invidious
|
||||
server att ansluta till.
|
||||
External Link Handling: "Välj standardförfarande när en länk klickas, som inte\
|
||||
\ kan öppnas i FreeTube är. \nStandard är att FreeTube kommer öppna länken i\
|
||||
\ din standardwebbläsare.\n"
|
||||
External Link Handling: "Välj standardförfarande när en länk klickas, som inte
|
||||
kan öppnas i FreeTube är. \nStandard är att FreeTube kommer öppna länken i din
|
||||
standardwebbläsare.\n"
|
||||
Privacy Settings:
|
||||
Remove Video Meta Files: Om vald, kommer FreeTube automatiskt att kasta metadata
|
||||
filer som skapades under uppspelning, när sidan stängs.
|
||||
|
@ -893,9 +922,15 @@ Tooltips:
|
|||
Invidiousinställningar påverkar inte externa videospelare.
|
||||
DefaultCustomArgumentsTemplate: "(Standard: '{defaultCustomArguments}')"
|
||||
Distraction Free Settings:
|
||||
Hide Channels: Skriv in kanalnamn eller kanal-ID för att dölja alla videor, spellistor
|
||||
och kanalen från att synas i sökfältet eller i populäraste kategorin. Kanalens
|
||||
namn måste skrivas in i sin helhet och är skiftlägeskänsligt.
|
||||
Hide Channels: Ange ett kanalnamn eller kanal-ID för att dölja alla videor, spellistor
|
||||
och själva kanalen från att visas i sökningar, trender, populäraste och rekommenderade.
|
||||
Det angivna kanalnamnet måste vara en fullständig matchning och är skiftlägeskänsligt.
|
||||
Experimental Settings:
|
||||
Replace HTTP Cache: Inaktiverar Electrons diskbaserade HTTP-cache och aktiverar
|
||||
en anpassad bildcache i minnet. Kommer att leda till ökad RAM-användning.
|
||||
SponsorBlock Settings:
|
||||
UseDeArrowTitles: Ersätt videotitlar med titlar som användare har skickat in från
|
||||
DeArrow.
|
||||
More: Mer
|
||||
Open New Window: Öppna nytt fönster
|
||||
Playing Next Video Interval: Spelar strax nästa video. Klicka för att stoppa. | Spelar
|
||||
|
@ -946,3 +981,7 @@ Chapters:
|
|||
Downloading has completed: '"{videoTitle}" har laddats ner'
|
||||
Starting download: Påbörjar nerladdning av "{videoTitle}"
|
||||
Downloading failed: Det uppstod ett problem med nerladdning av "{videoTitle}"
|
||||
Hashtag:
|
||||
Hashtag: Hashtaggar
|
||||
This hashtag does not currently have any videos: Den här hashtaggen har för närvarande
|
||||
inga videor
|
||||
|
|
|
@ -29,6 +29,13 @@ Close: 'Kapat'
|
|||
Back: 'Geri'
|
||||
Forward: 'İleri'
|
||||
|
||||
# Global
|
||||
# Anything shared among components / views should be put here
|
||||
Global:
|
||||
Videos: 'Videolar'
|
||||
Shorts: Kısa Videolar
|
||||
Live: Canlı
|
||||
|
||||
Version {versionNumber} is now available! Click for more details: '{versionNumber}
|
||||
sürümü çıktı! Daha fazla ayrıntı için tıklayın'
|
||||
Download From Site: 'Siteden indir'
|
||||
|
@ -88,6 +95,10 @@ Subscriptions:
|
|||
Disabled Automatic Fetching: Otomatik abonelik getirmeyi devre dışı bıraktınız.
|
||||
Abonelikleri burada görmek için yenileyin.
|
||||
Empty Channels: Abone olduğunuz kanallarda şu anda herhangi bir video yok.
|
||||
Subscriptions Tabs: Abonelikler Sekmeleri
|
||||
All Subscription Tabs Hidden: Tüm abonelik sekmeleri gizlidir. Buradaki içeriği
|
||||
görmek için lütfen "{settingsSection}" içindeki "{subsection}" bölümündeki bazı
|
||||
sekmelerin gizliliğini kaldırın.
|
||||
Trending:
|
||||
Trending: 'Öne Çıkanlar'
|
||||
Trending Tabs: Öne Çıkanlar Sekmeleri
|
||||
|
@ -410,6 +421,12 @@ Settings:
|
|||
Channel Page: Kanal Sayfası
|
||||
Watch Page: İzleme Sayfası
|
||||
General: Genel
|
||||
Subscriptions Page: Abonelikler Sayfası
|
||||
Hide Channel Releases: Kanal Yayınlarını Gizle
|
||||
Hide Channel Podcasts: Kanal Podcast'lerini Gizle
|
||||
Hide Subscriptions Videos: Abonelik Videolarını Gizle
|
||||
Hide Subscriptions Shorts: Abonelik Kısa Videolarını Gizle
|
||||
Hide Subscriptions Live: Abonelik Canlı Yayınlarını Gizle
|
||||
The app needs to restart for changes to take effect. Restart and apply change?: Değişikliklerin
|
||||
etkili olması için uygulamanın yeniden başlatılması gerekiyor. Yeniden başlatılsın
|
||||
ve değişiklikler uygulansın mı?
|
||||
|
@ -640,9 +657,16 @@ Channel:
|
|||
This channel does not currently have any live streams: Bu kanalda şu anda herhangi
|
||||
bir canlı yayın yok
|
||||
Shorts:
|
||||
Shorts: Kısa Videolar
|
||||
This channel does not currently have any shorts: Bu kanalda şu anda hiç kısa video
|
||||
yok
|
||||
Podcasts:
|
||||
Podcasts: Podcast'ler
|
||||
This channel does not currently have any podcasts: Bu kanalda şu anda herhangi
|
||||
bir podcast yok
|
||||
Releases:
|
||||
This channel does not currently have any releases: Bu kanalda şu anda herhangi
|
||||
bir yayın yok
|
||||
Releases: Yayınlar
|
||||
Video:
|
||||
Mark As Watched: 'İzlendi Olarak İşaretle'
|
||||
Remove From History: 'Geçmişden Kaldır'
|
||||
|
@ -948,9 +972,13 @@ Tooltips:
|
|||
ve özel bir bellek içi resim önbelleğini etkinleştirir. RAM kullanımının artmasına
|
||||
neden olacaktır.
|
||||
Distraction Free Settings:
|
||||
Hide Channels: Tüm videoların, oynatma listelerinin ve kanalın kendisinin arama
|
||||
veya öne çıkanlarda görünmesini engellemek için bir kanal adı veya kanal kimliği
|
||||
girin. Girilen kanal adı tam olarak eşleşmelidir ve büyük/küçük harfe duyarlıdır.
|
||||
Hide Channels: Tüm videoların, oynatma listelerinin ve kanalın kendisinin arama,
|
||||
öne çıkanlar, en popüler ve tavsiye edilenlerde görünmesini engellemek için
|
||||
bir kanal adı veya kanal kimliği girin. Girilen kanal adı tam olarak eşleşmelidir
|
||||
ve büyük/küçük harfe duyarlıdır.
|
||||
Hide Subscriptions Live: Bu ayar, "{settingsSection}" bölümünün "{subsection}"
|
||||
kısmında yer alan uygulama genelindeki "{appWideSetting}" ayarı tarafından geçersiz
|
||||
kılınıyor
|
||||
SponsorBlock Settings:
|
||||
UseDeArrowTitles: Video başlıklarını DeArrow'dan kullanıcıların gönderdiği başlıklarla
|
||||
değiştir.
|
||||
|
|
|
@ -29,6 +29,13 @@ Close: 'Закрити'
|
|||
Back: 'Назад'
|
||||
Forward: 'Вперед'
|
||||
|
||||
# Global
|
||||
# Anything shared among components / views should be put here
|
||||
Global:
|
||||
Videos: 'Відео'
|
||||
Shorts: Shorts
|
||||
Live: Наживо
|
||||
|
||||
Version {versionNumber} is now available! Click for more details: 'Доступна нова
|
||||
версія {versionNumber} ! Натисніть щоб побачити деталі'
|
||||
Download From Site: 'Завантажити з сайту'
|
||||
|
@ -89,6 +96,9 @@ Subscriptions:
|
|||
Disabled Automatic Fetching: Ви вимкнули автоматичне отримання підписок. Оновіть
|
||||
підписки, щоб вони з'явились тут.
|
||||
Empty Channels: Канали, на які ви підписалися, наразі не містять відео.
|
||||
Subscriptions Tabs: Вкладки підписок
|
||||
All Subscription Tabs Hidden: Усі вкладки підписки сховані. Щоб побачити вміст,
|
||||
будь ласка, відкрийте деякі вкладки в розділі "{subsection}" в "{settingsSection}".
|
||||
Trending:
|
||||
Trending: 'Популярне'
|
||||
Trending Tabs: Популярні вкладки
|
||||
|
@ -337,6 +347,12 @@ Settings:
|
|||
Channel Page: Сторінка каналу
|
||||
Watch Page: Сторінка перегляду
|
||||
General: Загальні
|
||||
Subscriptions Page: Сторінка підписок
|
||||
Hide Subscriptions Videos: Сховати відео з підписок
|
||||
Hide Channel Podcasts: Сховати канали подкастів
|
||||
Hide Channel Releases: Сховати канали випусків
|
||||
Hide Subscriptions Shorts: Сховати Shorts із підписок
|
||||
Hide Subscriptions Live: Сховати трансляції з підписок
|
||||
Data Settings:
|
||||
Data Settings: 'Налаштування даних'
|
||||
Select Import Type: 'Оберіть тип імпорту'
|
||||
|
@ -571,8 +587,15 @@ Channel:
|
|||
немає прямих трансляцій
|
||||
Live: Наживо
|
||||
Shorts:
|
||||
Shorts: Shorts
|
||||
This channel does not currently have any shorts: На цьому каналі немає Shorts
|
||||
Podcasts:
|
||||
Podcasts: Подкасти
|
||||
This channel does not currently have any podcasts: На цьому каналі наразі немає
|
||||
подкастів
|
||||
Releases:
|
||||
This channel does not currently have any releases: Наразі на цьому каналі немає
|
||||
випусків
|
||||
Releases: Випуски
|
||||
Video:
|
||||
Mark As Watched: 'Позначити переглянутим'
|
||||
Remove From History: 'Прибрати з історії'
|
||||
|
@ -865,8 +888,10 @@ Tooltips:
|
|||
у пам'яті. Призведе до збільшення використання оперативної пам'яті.
|
||||
Distraction Free Settings:
|
||||
Hide Channels: Введіть назву або ID каналу, щоб сховати всі відео, списки відтворення
|
||||
та сам канал від появи в пошуку або в тренді. Введена назва каналу повинна повністю
|
||||
збігатися і чутлива до регістру.
|
||||
та сам канал від появи в пошуку, тренді, найпопулярніших і рекомендованих. Введена
|
||||
назва каналу повинна повністю збігатися і чутлива до регістру.
|
||||
Hide Subscriptions Live: Цей параметр перевизначається загальнодоступним налаштуванням
|
||||
"{appWideSetting}" у розділі "{subsection}" "{settingsSection}"
|
||||
SponsorBlock Settings:
|
||||
UseDeArrowTitles: Замінити назви відео на надіслані користувачем назви з DeArrow.
|
||||
Local API Error (Click to copy): 'Помилка локального API (натисніть, щоб скопіювати)'
|
||||
|
|
|
@ -28,6 +28,11 @@ Close: 'Đóng'
|
|||
Back: 'Quay lại'
|
||||
Forward: 'Tiến tới'
|
||||
|
||||
# Global
|
||||
# Anything shared among components / views should be put here
|
||||
Global:
|
||||
Videos: 'Video'
|
||||
|
||||
# Search Bar
|
||||
Search / Go to URL: 'Tìm kiếm / Đi đến URL'
|
||||
# In Filter Button
|
||||
|
|
|
@ -27,6 +27,13 @@ Close: '关闭'
|
|||
Back: '后退'
|
||||
Forward: '前进'
|
||||
|
||||
# Global
|
||||
# Anything shared among components / views should be put here
|
||||
Global:
|
||||
Videos: '视频'
|
||||
Shorts: 短视频
|
||||
Live: 直播
|
||||
|
||||
# Search Bar
|
||||
Search / Go to URL: '搜索 / 前往URL'
|
||||
# In Filter Button
|
||||
|
@ -79,6 +86,9 @@ Subscriptions:
|
|||
Error Channels: 有错误的频道
|
||||
Disabled Automatic Fetching: 你仅用了自动订阅获取。刷新订阅在此处看到它们。
|
||||
Empty Channels: 你订阅的频道当前没有任何视频。
|
||||
Subscriptions Tabs: 订阅标签页
|
||||
All Subscription Tabs Hidden: 所有的订阅标签页均被隐藏。要在此查看内容,请在 "{subsection}" 部分取消隐藏某些标签页,此部分位于
|
||||
"{settingsSection}"。
|
||||
Trending:
|
||||
Trending: '热门'
|
||||
Trending Tabs: 流行标签
|
||||
|
@ -373,6 +383,12 @@ Settings:
|
|||
Channel Page: 频道页
|
||||
Watch Page: 观看页
|
||||
General: 常规
|
||||
Subscriptions Page: 订阅页
|
||||
Hide Channel Podcasts: 隐藏频道播客
|
||||
Hide Channel Releases: 隐藏频道发布
|
||||
Hide Subscriptions Shorts: 隐藏订阅短视频
|
||||
Hide Subscriptions Videos: 隐藏订阅视频
|
||||
Hide Subscriptions Live: 隐藏订阅直播
|
||||
The app needs to restart for changes to take effect. Restart and apply change?: 应用需要重启让修改生效。重启以应用修改?
|
||||
Proxy Settings:
|
||||
Proxy Protocol: 代理协议
|
||||
|
@ -534,7 +550,12 @@ Channel:
|
|||
This channel does not currently have any live streams: 此频道当前没有任何直播流
|
||||
Shorts:
|
||||
This channel does not currently have any shorts: 此频道目前没有任何短视频
|
||||
Shorts: 短视频
|
||||
Podcasts:
|
||||
This channel does not currently have any podcasts: 此频道当前无任何播客节目
|
||||
Podcasts: 播客
|
||||
Releases:
|
||||
Releases: 发布
|
||||
This channel does not currently have any releases: 此频道当前无任何发布
|
||||
Video:
|
||||
Open in YouTube: '在YouTube中打开'
|
||||
Copy YouTube Link: '复制YouTube链接'
|
||||
|
@ -837,7 +858,9 @@ Tooltips:
|
|||
Experimental Settings:
|
||||
Replace HTTP Cache: 禁用 Electron 基于磁盘的 HTTP 缓存,启用自定义内存中图像缓存。会增加内存的使用。
|
||||
Distraction Free Settings:
|
||||
Hide Channels: 输入频道名称或频道 ID 以隐藏所有视频、播放列表和频道本身,使其不出现在搜索结果或热门中。 输入的频道名称必须完全匹配,并且区分大小写。
|
||||
Hide Channels: 输入频道名称或频道 ID 使其中的所有视频、播放列表和频道本身不出现在搜索结果、时下流行、最受欢迎和推荐中。 输入的频道名称必须完全匹配,并且区分大小写。
|
||||
Hide Subscriptions Live: 此设置被应用级的 "{appWideSetting}" 设置所覆盖,"{appWideSetting}"
|
||||
位于 "{subsection}" 部分,该部分在 "{settingsSection}" 中
|
||||
SponsorBlock Settings:
|
||||
UseDeArrowTitles: 使用来自 DeArrow 的用户提交的标题替换原始视频标题。
|
||||
More: 更多
|
||||
|
|
|
@ -27,6 +27,13 @@ Close: '關閉'
|
|||
Back: '返回'
|
||||
Forward: '前進'
|
||||
|
||||
# Global
|
||||
# Anything shared among components / views should be put here
|
||||
Global:
|
||||
Videos: '影片'
|
||||
Shorts: 短片
|
||||
Live: 直播
|
||||
|
||||
# Search Bar
|
||||
Search / Go to URL: '搜尋/ 前往網址'
|
||||
# In Filter Button
|
||||
|
@ -79,6 +86,8 @@ Subscriptions:
|
|||
Error Channels: 有錯誤的頻道
|
||||
Disabled Automatic Fetching: 您已停用自動訂閱擷取。重新整理訂閱以在此處檢視。
|
||||
Empty Channels: 您訂閱的頻道目前沒有任何影片。
|
||||
Subscriptions Tabs: 訂閱分頁
|
||||
All Subscription Tabs Hidden: 所有訂閱分頁均已隱藏。要檢視此處的內容,請取消隱藏「{settingsSection}」中「{subsection}」區塊中的一些分頁。
|
||||
Trending:
|
||||
Trending: '發燒影片'
|
||||
Trending Tabs: 熱門分頁
|
||||
|
@ -375,6 +384,12 @@ Settings:
|
|||
Watch Page: 觀看頁面
|
||||
General: 一般
|
||||
Channel Page: 頻道頁面
|
||||
Subscriptions Page: 訂閱頁面
|
||||
Hide Channel Podcasts: 隱藏頻道 Podcast
|
||||
Hide Channel Releases: 隱藏頻道發布
|
||||
Hide Subscriptions Shorts: 隱藏訂閱短片
|
||||
Hide Subscriptions Videos: 隱藏訂閱影片
|
||||
Hide Subscriptions Live: 隱藏訂閱直播
|
||||
The app needs to restart for changes to take effect. Restart and apply change?: 此變更需要重啟讓修改生效。重啟並且套用變更?
|
||||
Proxy Settings:
|
||||
Error getting network information. Is your proxy configured properly?: 取得網路資訊時發生錯誤。您的代理伺服器設定正確嗎?
|
||||
|
@ -544,8 +559,13 @@ Channel:
|
|||
Live: 直播
|
||||
This channel does not currently have any live streams: 此頻道目前沒有任何直播
|
||||
Shorts:
|
||||
Shorts: 短片
|
||||
This channel does not currently have any shorts: 此頻道目前沒有任何短片
|
||||
Podcasts:
|
||||
This channel does not currently have any podcasts: 此頻道目前沒有 podcast
|
||||
Podcasts: Podcast
|
||||
Releases:
|
||||
Releases: 發布
|
||||
This channel does not currently have any releases: 此頻道目前沒有任何發布
|
||||
Video:
|
||||
Open in YouTube: '在YouTube中開啟'
|
||||
Copy YouTube Link: '複製YouTube連結'
|
||||
|
@ -850,7 +870,8 @@ Tooltips:
|
|||
Experimental Settings:
|
||||
Replace HTTP Cache: 停用 Electron 以磁碟為基礎的 HTTP 快取並啟用自訂的記憶體圖片快取。會導致記憶體使用量增加。
|
||||
Distraction Free Settings:
|
||||
Hide Channels: 輸入頻道名稱或頻道 ID 以隱藏所有影片、播放清單與頻道本身,使其完全不出現在搜尋或趨勢中。輸入的頻道名稱必須完全符合,且區分大小寫。
|
||||
Hide Channels: 輸入頻道名稱或頻道 ID 以隱藏所有影片、播放清單與頻道本身,使其完全不出現在搜尋、趨勢、熱門與建議中。輸入的頻道名稱必須完全符合,且區分大小寫。
|
||||
Hide Subscriptions Live: 此設定會被「{settingsSection}」的「{subsection}」部分中應用程式範圍的「{appWideSetting}」設定覆寫
|
||||
SponsorBlock Settings:
|
||||
UseDeArrowTitles: 將影片標題取代為 DeArrow 使用者遞交的標題。
|
||||
Playing Next Video Interval: 馬上播放下一個影片。點擊取消。| 播放下一個影片的時間為{nextVideoInterval}秒。點擊取消。|
|
||||
|
|
Loading…
Reference in New Issue