Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Live tab to channel pages #3273

Merged
merged 4 commits into from
Mar 15, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/renderer/store/modules/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ const actions = {
let urlType = 'unknown'

const channelPattern =
/^\/(?:(?:channel|user|c)\/)?(?<channelId>[^/]+)(?:\/(?<tab>join|featured|videos|playlists|about|community|channels))?\/?$/
/^\/(?:(?:channel|user|c)\/)?(?<channelId>[^/]+)(?:\/(?<tab>join|featured|videos|live|streams|playlists|about|community|channels))?\/?$/

const typePatterns = new Map([
['playlist', /^(\/playlist\/?|\/embed(\/?videoseries)?)$/],
Expand Down Expand Up @@ -421,6 +421,10 @@ const actions = {

let subPath = null
switch (match.groups.tab) {
case 'live':
case 'streams':
subPath = 'live'
break
case 'playlists':
subPath = 'playlists'
break
Expand Down
187 changes: 176 additions & 11 deletions src/renderer/views/Channel/Channel.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export default defineComponent({
subCount: 0,
searchPage: 2,
videoContinuationData: null,
liveContinuationData: null,
playlistContinuationData: null,
searchContinuationData: null,
communityContinuationData: null,
Expand All @@ -68,10 +69,12 @@ export default defineComponent({
joined: 0,
location: null,
videoSortBy: 'newest',
liveSortBy: 'newest',
playlistSortBy: 'newest',
lastSearchQuery: '',
relatedChannels: [],
latestVideos: [],
latestLive: [],
latestPlaylists: [],
latestCommunityPosts: [],
searchResults: [],
Expand All @@ -81,19 +84,13 @@ export default defineComponent({
errorMessage: '',
showSearchBar: true,
showShareMenu: true,
videoSelectValues: [
videoShortLiveSelectValues: [
'newest',
'popular'
],
playlistSelectValues: [
'newest',
'last'
],
tabInfoValues: [
'videos',
'playlists',
'community',
'about'
]
}
},
Expand Down Expand Up @@ -152,7 +149,7 @@ export default defineComponent({
}
},

videoSelectNames: function () {
videoShortLiveSelectNames: function () {
return [
this.$t('Channel.Videos.Sort Types.Newest'),
this.$t('Channel.Videos.Sort Types.Most Popular')
Expand Down Expand Up @@ -185,6 +182,8 @@ export default defineComponent({
switch (this.currentTab) {
case 'videos':
return !isNullOrEmpty(this.videoContinuationData)
case 'live':
return !isNullOrEmpty(this.liveContinuationData)
case 'playlists':
return !isNullOrEmpty(this.playlistContinuationData)
case 'community':
Expand All @@ -209,6 +208,28 @@ export default defineComponent({

hideSharingActions: function () {
return this.$store.getters.getHideSharingActions
},

hideLiveStreams: function () {
return this.$store.getters.getHideLiveStreams
},

tabInfoValues: function () {
const values = [
'videos',
'live',
'playlists',
'community',
'about'
]

// remove tabs from the array based on user settings
if (this.hideLiveStreams) {
const index = values.indexOf('live')
values.splice(index, 1)
}

return values
}
},
watch: {
Expand All @@ -222,7 +243,7 @@ export default defineComponent({
}

this.id = this.$route.params.id
this.currentTab = this.$route.params.currentTab ?? 'videos'
let currentTab = this.$route.params.currentTab ?? 'videos'
this.searchPage = 2
this.relatedChannels = []
this.latestVideos = []
Expand All @@ -232,11 +253,18 @@ export default defineComponent({
this.apiUsed = ''
this.channelInstance = ''
this.videoContinuationData = null
this.liveContinuationData = null
this.playlistContinuationData = null
this.searchContinuationData = null
this.communityContinuationData = null
this.showSearchBar = true

if (this.hideLiveStreams && currentTab === 'live') {
currentTab = 'videos'
}

this.currentTab = currentTab

if (this.id === '@@@') {
this.showShareMenu = false
this.setErrorMessage(this.$i18n.t('Channel.This channel does not exist'))
Expand Down Expand Up @@ -268,6 +296,21 @@ export default defineComponent({
}
},

liveSortBy () {
this.isElementListLoading = true
this.latestLive = []
switch (this.apiUsed) {
case 'local':
this.getChannelLiveLocal()
break
case 'invidious':
this.channelInvidiousLive(true)
break
default:
this.getChannelLiveLocal()
}
},

playlistSortBy () {
this.isElementListLoading = true
this.latestPlaylists = []
Expand All @@ -293,7 +336,14 @@ export default defineComponent({
}

this.id = this.$route.params.id
this.currentTab = this.$route.params.currentTab ?? 'videos'

let currentTab = this.$route.params.currentTab ?? 'videos'

if (this.hideLiveStreams && currentTab === 'live') {
currentTab = 'videos'
}

this.currentTab = currentTab

if (this.id === '@@@') {
this.showShareMenu = false
Expand Down Expand Up @@ -504,6 +554,10 @@ export default defineComponent({
this.getChannelVideosLocal()
}

if (!this.hideLiveStreams && channel.has_live_streams) {
this.getChannelLiveLocal()
}

if (channel.has_playlists) {
this.getChannelPlaylistsLocal()
}
Expand Down Expand Up @@ -573,7 +627,7 @@ export default defineComponent({
let videosTab = await channel.getVideos()

if (this.videoSortBy !== 'newest') {
const index = this.videoSelectValues.indexOf(this.videoSortBy)
const index = this.videoShortLiveSelectValues.indexOf(this.videoSortBy)
videosTab = await videosTab.applyFilter(videosTab.filters[index])
}

Expand Down Expand Up @@ -617,6 +671,62 @@ export default defineComponent({
}
},

getChannelLiveLocal: async function () {
this.isElementListLoading = true
const expectedId = this.id

try {
/**
* @type {import('youtubei.js/dist/src/parser/youtube/Channel').default}
*/
const channel = this.channelInstance
let liveTab = await channel.getLiveStreams()

if (this.liveSortBy !== 'newest') {
const index = this.videoShortLiveSelectValues.indexOf(this.liveSortBy)
liveTab = await liveTab.applyFilter(liveTab.filters[index])
}

if (expectedId !== this.id) {
return
}

this.latestLive = parseLocalChannelVideos(liveTab.videos, channel.header.author)
this.liveContinuationData = liveTab.has_continuation ? liveTab : 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.getChannelInfoInvidious()
} else {
this.isLoading = false
}
}
},

getChannelLiveLocalMore: async function () {
try {
/**
* @type {import('youtubei.js/dist/src/parser/youtube/Channel').ChannelListContinuation|import('youtubei.js/dist/src/parser/youtube/Channel').FilteredChannelList}
*/
const continuation = await this.liveContinuationData.getContinuation()

this.latestLive.push(...parseLocalChannelVideos(continuation.videos, this.channelInstance.header.author))
this.liveContinuationData = 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)
})
}
},

getChannelInfoInvidious: function () {
this.isLoading = true
this.apiUsed = 'invidious'
Expand Down Expand Up @@ -670,6 +780,10 @@ export default defineComponent({
this.channelInvidiousVideos()
}

if (!this.hideLiveStreams && response.tabs.includes('streams')) {
this.channelInvidiousLive()
}

if (response.tabs.includes('playlists')) {
this.getPlaylistsInvidious()
}
Expand Down Expand Up @@ -735,6 +849,47 @@ export default defineComponent({
})
},

channelInvidiousLive: function (sortByChanged) {
const payload = {
resource: 'channels',
id: this.id,
subResource: 'streams',
params: {
sort_by: this.liveSortBy,
}
}

if (sortByChanged) {
this.liveContinuationData = null
}

let more = false
if (this.liveContinuationData) {
payload.params.continuation = this.liveContinuationData
more = true
}

if (!more) {
this.isElementListLoading = true
}

invidiousAPICall(payload).then((response) => {
if (more) {
this.latestLive.push(...response.videos)
} else {
this.latestLive = response.videos
}
this.liveContinuationData = response.continuation || null
this.isElementListLoading = false
}).catch((err) => {
console.error(err)
const errorMessage = this.$t('Invidious API Error (Click to copy)')
showToast(`${errorMessage}: ${err}`, 10000, () => {
copyToClipboard(err)
})
})
},

getChannelPlaylistsLocal: async function () {
const expectedId = this.id

Expand Down Expand Up @@ -1025,6 +1180,16 @@ export default defineComponent({
break
}
break
case 'live':
switch (this.apiUsed) {
case 'local':
this.getChannelLiveLocalMore()
break
case 'invidious':
this.channelInvidiousLive()
break
}
break
case 'playlists':
switch (this.apiUsed) {
case 'local':
Expand Down
Loading