2023-01-18 08:50:02 +01:00
|
|
|
import { defineComponent } from 'vue'
|
2020-05-23 23:29:42 +02:00
|
|
|
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 autolinker from 'autolinker'
|
2022-11-09 06:57:48 +01:00
|
|
|
import { getRandomColorClass } from '../../helpers/colors'
|
2023-01-13 17:54:22 +01:00
|
|
|
import { getLocalVideoInfo, parseLocalTextRuns } from '../../helpers/api/local'
|
2023-11-21 12:05:21 +01:00
|
|
|
import { formatNumber } from '../../helpers/utils'
|
2020-05-23 23:29:42 +02:00
|
|
|
|
2023-01-18 08:50:02 +01:00
|
|
|
export default defineComponent({
|
2020-05-23 23:29:42 +02:00
|
|
|
name: 'WatchVideoLiveChat',
|
|
|
|
components: {
|
|
|
|
'ft-loader': FtLoader,
|
|
|
|
'ft-card': FtCard,
|
2022-10-27 11:34:39 +02:00
|
|
|
'ft-button': FtButton
|
2020-05-23 23:29:42 +02:00
|
|
|
},
|
|
|
|
props: {
|
2023-01-13 17:54:22 +01:00
|
|
|
liveChat: {
|
|
|
|
type: EventTarget,
|
|
|
|
default: null
|
|
|
|
},
|
2020-05-23 23:29:42 +02:00
|
|
|
videoId: {
|
|
|
|
type: String,
|
|
|
|
required: true
|
|
|
|
},
|
2023-01-13 17:54:22 +01:00
|
|
|
channelId: {
|
2020-05-23 23:29:42 +02:00
|
|
|
type: String,
|
|
|
|
required: true
|
|
|
|
}
|
|
|
|
},
|
|
|
|
data: function () {
|
|
|
|
return {
|
2023-11-21 12:05:21 +01:00
|
|
|
/** @type {import('youtubei.js').YT.LiveChat|null} */
|
2023-01-13 17:54:22 +01:00
|
|
|
liveChatInstance: null,
|
2020-05-23 23:29:42 +02:00
|
|
|
isLoading: true,
|
|
|
|
hasError: false,
|
|
|
|
hasEnded: false,
|
|
|
|
showEnableChat: false,
|
|
|
|
errorMessage: '',
|
|
|
|
stayAtBottom: true,
|
|
|
|
showSuperChat: false,
|
|
|
|
showScrollToBottom: false,
|
|
|
|
comments: [],
|
|
|
|
superChatComments: [],
|
|
|
|
superChat: {
|
2023-01-13 17:54:22 +01:00
|
|
|
id: '',
|
2020-05-23 23:29:42 +02:00
|
|
|
author: {
|
|
|
|
name: '',
|
2023-01-13 17:54:22 +01:00
|
|
|
thumbnailUrl: ''
|
2020-05-23 23:29:42 +02:00
|
|
|
},
|
2023-01-13 17:54:22 +01:00
|
|
|
message: '',
|
2020-05-23 23:29:42 +02:00
|
|
|
superChat: {
|
2023-01-13 17:54:22 +01:00
|
|
|
amount: '',
|
|
|
|
colorClass: ''
|
2020-05-23 23:29:42 +02:00
|
|
|
}
|
2023-11-21 12:05:21 +01:00
|
|
|
},
|
|
|
|
/** @type {number|null} */
|
|
|
|
watchingCount: null,
|
2020-05-23 23:29:42 +02:00
|
|
|
}
|
|
|
|
},
|
|
|
|
computed: {
|
|
|
|
backendPreference: function () {
|
|
|
|
return this.$store.getters.getBackendPreference
|
|
|
|
},
|
|
|
|
|
|
|
|
backendFallback: function () {
|
|
|
|
return this.$store.getters.getBackendFallback
|
|
|
|
},
|
|
|
|
|
|
|
|
chatHeight: function () {
|
|
|
|
if (this.superChatComments.length > 0) {
|
|
|
|
return '390px'
|
|
|
|
} else {
|
|
|
|
return '445px'
|
|
|
|
}
|
2020-10-06 04:27:32 +02:00
|
|
|
},
|
2022-10-04 19:55:13 +02:00
|
|
|
|
|
|
|
scrollingBehaviour: function () {
|
|
|
|
return this.$store.getters.getDisableSmoothScrolling ? 'auto' : 'smooth'
|
2023-11-21 12:05:21 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
hideVideoViews: function () {
|
|
|
|
return this.$store.getters.getHideVideoViews
|
|
|
|
},
|
|
|
|
|
|
|
|
formattedWatchingCount: function () {
|
|
|
|
return this.watchingCount !== null ? formatNumber(this.watchingCount) : '0'
|
2020-05-23 23:29:42 +02:00
|
|
|
}
|
|
|
|
},
|
2023-01-13 17:54:22 +01:00
|
|
|
beforeDestroy: function () {
|
|
|
|
this.hasEnded = true
|
|
|
|
this.liveChatInstance?.stop()
|
|
|
|
this.liveChatInstance = null
|
|
|
|
},
|
2020-05-23 23:29:42 +02:00
|
|
|
created: function () {
|
2022-09-15 10:59:09 +02:00
|
|
|
if (!process.env.IS_ELECTRON) {
|
2020-05-23 23:29:42 +02:00
|
|
|
this.hasError = true
|
2020-08-08 04:16:06 +02:00
|
|
|
this.errorMessage = this.$t('Video["Live Chat is currently not supported in this build."]')
|
2023-04-19 07:37:42 +02:00
|
|
|
this.isLoading = false
|
2020-05-23 23:29:42 +02:00
|
|
|
} else {
|
|
|
|
switch (this.backendPreference) {
|
|
|
|
case 'local':
|
2023-03-23 02:11:25 +01:00
|
|
|
if (this.liveChat) {
|
|
|
|
this.liveChatInstance = this.liveChat
|
|
|
|
this.startLiveChatLocal()
|
|
|
|
} else {
|
|
|
|
this.showLiveChatUnavailable()
|
|
|
|
}
|
2020-05-23 23:29:42 +02:00
|
|
|
break
|
|
|
|
case 'invidious':
|
|
|
|
if (this.backendFallback) {
|
|
|
|
this.getLiveChatLocal()
|
|
|
|
} else {
|
|
|
|
this.hasError = true
|
2023-04-09 00:50:52 +02:00
|
|
|
this.errorMessage = this.$t('Video["Live Chat is currently not supported with the Invidious API. A direct connection to YouTube is required."]')
|
2020-05-23 23:29:42 +02:00
|
|
|
this.showEnableChat = true
|
|
|
|
this.isLoading = false
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
methods: {
|
|
|
|
enableLiveChat: function () {
|
|
|
|
this.hasError = false
|
|
|
|
this.showEnableChat = false
|
|
|
|
this.isLoading = true
|
|
|
|
this.getLiveChatLocal()
|
|
|
|
},
|
|
|
|
|
2023-01-13 17:54:22 +01:00
|
|
|
getLiveChatLocal: async function () {
|
|
|
|
const videoInfo = await getLocalVideoInfo(this.videoId)
|
2020-05-23 23:29:42 +02:00
|
|
|
|
2023-03-23 02:11:25 +01:00
|
|
|
if (videoInfo.livechat) {
|
|
|
|
this.liveChatInstance = videoInfo.getLiveChat()
|
|
|
|
|
|
|
|
this.startLiveChatLocal()
|
|
|
|
} else {
|
|
|
|
this.showLiveChatUnavailable()
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
showLiveChatUnavailable: function () {
|
|
|
|
this.hasError = true
|
|
|
|
this.errorMessage = this.$t('Video["Live Chat is unavailable for this stream. It may have been disabled by the uploader."]')
|
|
|
|
this.isLoading = false
|
|
|
|
this.showEnableChat = false
|
2023-01-13 17:54:22 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
startLiveChatLocal: function () {
|
|
|
|
this.liveChatInstance.once('start', initialData => {
|
|
|
|
/**
|
|
|
|
* @type {import ('youtubei.js/dist/src/parser/index').LiveChatContinuation}
|
|
|
|
*/
|
|
|
|
const liveChatContinuation = initialData
|
|
|
|
|
|
|
|
const actions = liveChatContinuation.actions.filter(action => action.type === 'AddChatItemAction')
|
|
|
|
|
|
|
|
for (const { item } of actions) {
|
|
|
|
switch (item.type) {
|
|
|
|
case 'LiveChatTextMessage':
|
|
|
|
this.parseLiveChatComment(item)
|
|
|
|
break
|
|
|
|
case 'LiveChatPaidMessage':
|
|
|
|
this.parseLiveChatSuperChat(item)
|
|
|
|
}
|
|
|
|
}
|
2020-05-23 23:29:42 +02:00
|
|
|
|
|
|
|
this.isLoading = false
|
2023-01-13 17:54:22 +01:00
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
this.$refs.liveChatComments?.scrollTo({
|
|
|
|
top: this.$refs.liveChatComments.scrollHeight,
|
|
|
|
behavior: 'instant'
|
|
|
|
})
|
|
|
|
})
|
2020-05-23 23:29:42 +02:00
|
|
|
})
|
|
|
|
|
2023-01-13 17:54:22 +01:00
|
|
|
this.liveChatInstance.on('chat-update', action => {
|
|
|
|
if (this.hasEnded) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if (action.type === 'AddChatItemAction') {
|
|
|
|
switch (action.item.type) {
|
|
|
|
case 'LiveChatTextMessage':
|
|
|
|
this.parseLiveChatComment(action.item)
|
|
|
|
break
|
|
|
|
case 'LiveChatPaidMessage':
|
|
|
|
this.parseLiveChatSuperChat(action.item)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2020-05-23 23:29:42 +02:00
|
|
|
})
|
|
|
|
|
2023-11-21 12:05:21 +01:00
|
|
|
this.liveChatInstance.on('metadata-update', metadata => {
|
|
|
|
if (!this.hideVideoViews && metadata.views && !isNaN(metadata.views.original_view_count)) {
|
|
|
|
this.watchingCount = metadata.views.original_view_count
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2023-01-13 17:54:22 +01:00
|
|
|
this.liveChatInstance.once('end', () => {
|
|
|
|
this.hasEnded = true
|
|
|
|
this.liveChatInstance = null
|
2020-05-23 23:29:42 +02:00
|
|
|
})
|
|
|
|
|
2023-01-13 17:54:22 +01:00
|
|
|
this.liveChatInstance.once('error', error => {
|
|
|
|
this.liveChatInstance.stop()
|
|
|
|
this.liveChatInstance = null
|
|
|
|
console.error(error)
|
|
|
|
this.errorMessage = error
|
|
|
|
this.hasError = true
|
|
|
|
this.isLoading = false
|
|
|
|
this.hasEnded = true
|
2020-05-23 23:29:42 +02:00
|
|
|
})
|
|
|
|
|
2023-01-13 17:54:22 +01:00
|
|
|
this.liveChatInstance.start()
|
2020-05-23 23:29:42 +02:00
|
|
|
},
|
|
|
|
|
2023-01-13 17:54:22 +01:00
|
|
|
/**
|
2023-05-01 15:12:23 +02:00
|
|
|
* @param {import('youtubei.js').YTNodes.LiveChatTextMessage} comment
|
2023-01-13 17:54:22 +01:00
|
|
|
*/
|
2020-05-23 23:29:42 +02:00
|
|
|
parseLiveChatComment: function (comment) {
|
2023-01-13 17:54:22 +01:00
|
|
|
/**
|
|
|
|
* can also be undefined if there is no badge
|
2023-05-01 15:12:23 +02:00
|
|
|
* @type {import('youtubei.js').YTNodes.LiveChatAuthorBadge}
|
2023-01-13 17:54:22 +01:00
|
|
|
*/
|
|
|
|
const badge = comment.author.badges.find(badge => badge.type === 'LiveChatAuthorBadge' && badge.custom_thumbnail)
|
2020-05-23 23:29:42 +02:00
|
|
|
|
2023-01-13 17:54:22 +01:00
|
|
|
const parsedComment = {
|
|
|
|
message: autolinker.link(parseLocalTextRuns(comment.message.runs, 20)),
|
|
|
|
author: {
|
2023-05-17 16:24:15 +02:00
|
|
|
name: comment.author.name,
|
2023-01-13 17:54:22 +01:00
|
|
|
thumbnailUrl: comment.author.thumbnails.at(-1).url,
|
|
|
|
isOwner: comment.author.id === this.channelId,
|
|
|
|
isModerator: comment.author.is_moderator,
|
|
|
|
isMember: !!badge
|
2020-05-28 04:48:41 +02:00
|
|
|
}
|
2020-05-23 23:29:42 +02:00
|
|
|
}
|
|
|
|
|
2023-01-13 17:54:22 +01:00
|
|
|
if (badge) {
|
|
|
|
parsedComment.badge = {
|
2023-05-17 16:24:15 +02:00
|
|
|
url: badge.custom_thumbnail.at(-1)?.url,
|
2023-01-13 17:54:22 +01:00
|
|
|
tooltip: badge.tooltip ?? ''
|
|
|
|
}
|
2020-05-23 23:29:42 +02:00
|
|
|
}
|
|
|
|
|
2023-01-13 17:54:22 +01:00
|
|
|
this.pushComment(parsedComment)
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2023-05-01 15:12:23 +02:00
|
|
|
* @param {import('youtubei.js').YTNodes.LiveChatPaidMessage} superChat
|
2023-01-13 17:54:22 +01:00
|
|
|
*/
|
|
|
|
parseLiveChatSuperChat: function (superChat) {
|
|
|
|
const parsedComment = {
|
|
|
|
id: superChat.id,
|
|
|
|
message: autolinker.link(parseLocalTextRuns(superChat.message.runs, 20)),
|
|
|
|
author: {
|
|
|
|
name: superChat.author.name.text,
|
|
|
|
thumbnailUrl: superChat.author.thumbnails[0].url
|
|
|
|
},
|
|
|
|
superChat: {
|
|
|
|
amount: superChat.purchase_amount,
|
2022-10-12 08:49:12 +02:00
|
|
|
colorClass: getRandomColorClass()
|
|
|
|
}
|
2023-01-13 17:54:22 +01:00
|
|
|
}
|
2020-05-23 23:29:42 +02:00
|
|
|
|
2023-01-13 17:54:22 +01:00
|
|
|
this.superChatComments.unshift(parsedComment)
|
2020-05-23 23:29:42 +02:00
|
|
|
|
2023-01-13 17:54:22 +01:00
|
|
|
setTimeout(() => {
|
|
|
|
this.removeFromSuperChat(parsedComment.id)
|
|
|
|
}, 120000)
|
|
|
|
|
|
|
|
this.pushComment(parsedComment)
|
|
|
|
},
|
|
|
|
|
|
|
|
pushComment: function (comment) {
|
|
|
|
this.comments.push(comment)
|
2020-05-23 23:29:42 +02:00
|
|
|
|
2023-01-13 17:54:22 +01:00
|
|
|
if (!this.isLoading && this.stayAtBottom) {
|
2022-10-04 19:55:13 +02:00
|
|
|
setTimeout(() => {
|
|
|
|
this.$refs.liveChatComments?.scrollTo({
|
|
|
|
top: this.$refs.liveChatComments.scrollHeight,
|
|
|
|
behavior: this.scrollingBehaviour
|
|
|
|
})
|
|
|
|
})
|
2020-05-23 23:29:42 +02:00
|
|
|
}
|
2020-10-17 05:01:32 +02:00
|
|
|
|
2020-12-17 20:11:13 +01:00
|
|
|
if (this.comments.length > 150 && this.stayAtBottom) {
|
2020-10-17 05:01:32 +02:00
|
|
|
this.comments = this.comments.splice(this.comments.length - 150, this.comments.length)
|
|
|
|
}
|
2020-05-23 23:29:42 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
removeFromSuperChat: function (id) {
|
|
|
|
this.superChatComments = this.superChatComments.filter((comment) => {
|
|
|
|
return comment.id !== id
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
|
|
|
showSuperChatComment: function (comment) {
|
|
|
|
if (this.superChat.id === comment.id && this.showSuperChat) {
|
|
|
|
this.showSuperChat = false
|
|
|
|
} else {
|
|
|
|
this.superChat = comment
|
|
|
|
this.showSuperChat = true
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
onScroll: function (event) {
|
2022-10-04 19:55:13 +02:00
|
|
|
const liveChatComments = this.$refs.liveChatComments
|
2020-05-23 23:29:42 +02:00
|
|
|
if (event.wheelDelta >= 0 && this.stayAtBottom) {
|
|
|
|
this.stayAtBottom = false
|
|
|
|
|
|
|
|
if (liveChatComments.scrollHeight > liveChatComments.clientHeight) {
|
|
|
|
this.showScrollToBottom = true
|
|
|
|
}
|
|
|
|
} else if (event.wheelDelta < 0 && !this.stayAtBottom) {
|
|
|
|
if ((liveChatComments.scrollHeight - liveChatComments.scrollTop) === liveChatComments.clientHeight) {
|
|
|
|
this.scrollToBottom()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
scrollToBottom: function () {
|
2022-10-04 19:55:13 +02:00
|
|
|
this.$refs.liveChatComments.scrollTo({
|
|
|
|
top: this.$refs.liveChatComments.scrollHeight,
|
|
|
|
behavior: this.scrollingBehaviour
|
|
|
|
})
|
2020-05-23 23:29:42 +02:00
|
|
|
this.stayAtBottom = true
|
|
|
|
this.showScrollToBottom = false
|
2022-10-12 08:49:12 +02:00
|
|
|
}
|
2020-05-23 23:29:42 +02:00
|
|
|
}
|
|
|
|
})
|