Skip to content

Commit

Permalink
Add Live tab to channel pages (#3273)
Browse files Browse the repository at this point in the history
* Add Live tab to channel pages

* Handle invidious streams tab URL

* Clear live videos when navigating between channels

* Reset sort by when changing channels
  • Loading branch information
absidue committed Mar 15, 2023
1 parent a878ff3 commit 0212467
Show file tree
Hide file tree
Showing 4 changed files with 229 additions and 15 deletions.
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
189 changes: 178 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,21 +243,30 @@ 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 = []
this.latestLive = []
this.liveSortBy = 'newest'
this.latestPlaylists = []
this.searchResults = []
this.shownElementList = []
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 +298,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 +338,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 +556,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 +629,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 +673,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 +782,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 +851,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 +1182,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

0 comments on commit 0212467

Please sign in to comment.