Add Support for Live Videos and Live Video Chat
This commit is contained in:
parent
8980dc74d2
commit
009174b89b
|
@ -65,17 +65,29 @@ if (isDevMode) {
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
config.plugins.push(
|
config.plugins.push(
|
||||||
new CopyWebpackPlugin([
|
new CopyWebpackPlugin({
|
||||||
{
|
patterns: [
|
||||||
from: path.join(__dirname, '../src/data'),
|
{
|
||||||
to: path.join(__dirname, '../dist/data'),
|
from: path.join(__dirname, '../static/pwabuilder-sw.js'),
|
||||||
},
|
to: path.join(__dirname, '../dist/web/pwabuilder-sw.js'),
|
||||||
{
|
},
|
||||||
from: path.join(__dirname, '../static'),
|
{
|
||||||
to: path.join(__dirname, '../dist/static'),
|
from: path.join(__dirname, '../static'),
|
||||||
ignore: ['.*'],
|
to: path.join(__dirname, '../dist/web/static'),
|
||||||
},
|
globOptions: {
|
||||||
]),
|
ignore: ['.*', 'pwabuilder-sw.js'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: path.join(__dirname, '../_icons'),
|
||||||
|
to: path.join(__dirname, '../dist/web/_icons'),
|
||||||
|
globOptions: {
|
||||||
|
ignore: ['.*'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
),
|
||||||
new webpack.LoaderOptionsPlugin({
|
new webpack.LoaderOptionsPlugin({
|
||||||
minimize: true,
|
minimize: true,
|
||||||
})
|
})
|
||||||
|
|
|
@ -151,13 +151,29 @@ if (isDevMode) {
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
config.plugins.push(
|
config.plugins.push(
|
||||||
new CopyWebpackPlugin([
|
new CopyWebpackPlugin({
|
||||||
{
|
patterns: [
|
||||||
from: path.join(__dirname, '../static'),
|
{
|
||||||
to: path.join(__dirname, '../dist/static'),
|
from: path.join(__dirname, '../static/pwabuilder-sw.js'),
|
||||||
ignore: ['.*'],
|
to: path.join(__dirname, '../dist/web/pwabuilder-sw.js'),
|
||||||
},
|
},
|
||||||
]),
|
{
|
||||||
|
from: path.join(__dirname, '../static'),
|
||||||
|
to: path.join(__dirname, '../dist/web/static'),
|
||||||
|
globOptions: {
|
||||||
|
ignore: ['.*', 'pwabuilder-sw.js'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: path.join(__dirname, '../_icons'),
|
||||||
|
to: path.join(__dirname, '../dist/web/_icons'),
|
||||||
|
globOptions: {
|
||||||
|
ignore: ['.*'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
),
|
||||||
new webpack.LoaderOptionsPlugin({
|
new webpack.LoaderOptionsPlugin({
|
||||||
minimize: true,
|
minimize: true,
|
||||||
})
|
})
|
||||||
|
|
|
@ -154,22 +154,29 @@ if (isDevMode) {
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
config.plugins.push(
|
config.plugins.push(
|
||||||
new CopyWebpackPlugin([
|
new CopyWebpackPlugin({
|
||||||
{
|
patterns: [
|
||||||
from: path.join(__dirname, '../static/pwabuilder-sw.js'),
|
{
|
||||||
to: path.join(__dirname, '../dist/web/pwabuilder-sw.js'),
|
from: path.join(__dirname, '../static/pwabuilder-sw.js'),
|
||||||
},
|
to: path.join(__dirname, '../dist/web/pwabuilder-sw.js'),
|
||||||
{
|
},
|
||||||
from: path.join(__dirname, '../static'),
|
{
|
||||||
to: path.join(__dirname, '../dist/web/static'),
|
from: path.join(__dirname, '../static'),
|
||||||
ignore: ['.*', 'pwabuilder-sw.js'],
|
to: path.join(__dirname, '../dist/web/static'),
|
||||||
},
|
globOptions: {
|
||||||
{
|
ignore: ['.*', 'pwabuilder-sw.js'],
|
||||||
from: path.join(__dirname, '../_icons'),
|
},
|
||||||
to: path.join(__dirname, '../dist/web/_icons'),
|
},
|
||||||
ignore: ['.*'],
|
{
|
||||||
},
|
from: path.join(__dirname, '../_icons'),
|
||||||
]),
|
to: path.join(__dirname, '../dist/web/_icons'),
|
||||||
|
globOptions: {
|
||||||
|
ignore: ['.*'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
),
|
||||||
new webpack.LoaderOptionsPlugin({
|
new webpack.LoaderOptionsPlugin({
|
||||||
minimize: true,
|
minimize: true,
|
||||||
})
|
})
|
||||||
|
|
|
@ -95,9 +95,9 @@
|
||||||
},
|
},
|
||||||
"license": "GPL-3.0-or-later",
|
"license": "GPL-3.0-or-later",
|
||||||
"main": "./dist/main.js",
|
"main": "./dist/main.js",
|
||||||
"name": "freetube",
|
"name": "freetube-vue",
|
||||||
"private": true,
|
"private": true,
|
||||||
"productName": "FreeTube",
|
"productName": "FreeTube-Vue",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/mubaidr/vue-electron-template.git"
|
"url": "git+https://github.com/mubaidr/vue-electron-template.git"
|
||||||
|
|
|
@ -191,11 +191,7 @@ export default Vue.extend({
|
||||||
this.hideViews = true
|
this.hideViews = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof (this.data.uploaded_at) !== 'undefined' && this.data.uploaded_at !== null && this.data.uploaded_at.includes('watching')) {
|
this.isLive = this.data.live
|
||||||
const uploadSplit = this.data.uploaded_at.split(' ')
|
|
||||||
this.viewCount = parseInt(uploadSplit[0])
|
|
||||||
this.isLive = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -217,7 +217,7 @@ export default Vue.extend({
|
||||||
src: this.storyboardSrc
|
src: this.storyboardSrc
|
||||||
})
|
})
|
||||||
|
|
||||||
if (this.useDash) {
|
if (this.useDash || this.useHls) {
|
||||||
this.dataSetup.plugins.httpSourceSelector = {
|
this.dataSetup.plugins.httpSourceSelector = {
|
||||||
default: 'auto'
|
default: 'auto'
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,211 @@
|
||||||
|
.relative {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messageContainer {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
font-size: 18px;
|
||||||
|
color: var(--tertiary-text-color);
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.errorIcon {
|
||||||
|
width: 100%;
|
||||||
|
color: var(--tertiary-text-color);
|
||||||
|
font-size: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.enableLiveChat {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.superChatComments {
|
||||||
|
width: 100%;
|
||||||
|
height: 50px;
|
||||||
|
overflow-x: auto;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.superChat {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 1px;
|
||||||
|
padding-right: 10px;
|
||||||
|
margin-left: 2px;
|
||||||
|
margin-right: 2px;
|
||||||
|
height: 30px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 200px 200px 200px 200px;
|
||||||
|
-webkit-border-radius: 200px 200px 200px 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.superChatContent {
|
||||||
|
margin-left: 32px;
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.superChat .channelThumbnail {
|
||||||
|
margin-top: 3px;
|
||||||
|
margin-left: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.donationAmount {
|
||||||
|
color: var(--text-with-main-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.openedSuperChat {
|
||||||
|
background-color: rgba(0, 0, 0, 0.7);
|
||||||
|
width: 100%;
|
||||||
|
height: 415px;
|
||||||
|
position: absolute;
|
||||||
|
margin-left: -16px;
|
||||||
|
padding-right: 32px;
|
||||||
|
bottom: -15px;
|
||||||
|
cursor: auto;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.openedSuperChat .superChatMessage {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.superChatMessage {
|
||||||
|
width: 90%;
|
||||||
|
margin-left: 5%;
|
||||||
|
margin-right: 5%;
|
||||||
|
margin-top: 10px;
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
border-radius: 5px 5px 5px 5px;
|
||||||
|
-webkit-border-radius: 5px 5px 5px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upperSuperChatMessage {
|
||||||
|
margin-top: -15px;
|
||||||
|
width: 100%;
|
||||||
|
height: 55px;
|
||||||
|
background-color: var(--primary-color-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.upperSuperChatMessage .channelThumbnail {
|
||||||
|
width: 45px;
|
||||||
|
margin-left: 10px;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upperSuperChatMessage .channelName {
|
||||||
|
color: var(--text-with-main-color);
|
||||||
|
opacity: 0.7;
|
||||||
|
position: relative;
|
||||||
|
top: 5px;
|
||||||
|
margin-left: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upperSuperChatMessage .donationAmount {
|
||||||
|
color: var(--text-with-main-color);
|
||||||
|
font-weight: bold;
|
||||||
|
margin-left: 65px;
|
||||||
|
position: relative;
|
||||||
|
bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.superChatMessage .chatMessage {
|
||||||
|
color: var(--text-with-main-color);
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.liveChatComments {
|
||||||
|
width: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment .superChatMessage {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment .upperSuperChatMessage {
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment {
|
||||||
|
width: 100%;
|
||||||
|
padding-top: 5px;
|
||||||
|
padding-bottom: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.channelThumbnail {
|
||||||
|
width: 25px;
|
||||||
|
float: left;
|
||||||
|
border-radius: 200px 200px 200px 200px;
|
||||||
|
-webkit-border-radius: 200px 200px 200px 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chatContent {
|
||||||
|
margin-left: 30px;
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
font-size: 12px;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.channelName {
|
||||||
|
color: var(--tertiary-text-color);
|
||||||
|
font-weight: bold;
|
||||||
|
padding-left: 5px;
|
||||||
|
padding-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member {
|
||||||
|
color: #4CAF50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.moderator {
|
||||||
|
color: #2196F3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.owner {
|
||||||
|
margin-right: 2px;
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
color: var(--text-with-main-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badgeImage {
|
||||||
|
width: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrollToBottom {
|
||||||
|
background-color: var(--accent-color);
|
||||||
|
width: 35px;
|
||||||
|
height: 35px;
|
||||||
|
position: absolute;
|
||||||
|
left: 45%;
|
||||||
|
bottom: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 200px 200px 200px 200px;
|
||||||
|
-webkit-border-radius: 200px 200px 200px 200px;
|
||||||
|
transition: background 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrollToBottom:hover {
|
||||||
|
background-color: var(--accent-color-light);
|
||||||
|
transition: background 0.2s ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
color: var(--text-with-accent-color);
|
||||||
|
font-size: 22px;
|
||||||
|
position: relative;
|
||||||
|
left: 0.5rem;
|
||||||
|
top: 0.45rem;
|
||||||
|
}
|
|
@ -0,0 +1,240 @@
|
||||||
|
import Vue 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 FtListVideo from '../ft-list-video/ft-list-video.vue'
|
||||||
|
|
||||||
|
import $ from 'jquery'
|
||||||
|
import autolinker from 'autolinker'
|
||||||
|
import { LiveChat } from 'youtube-chat'
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
name: 'WatchVideoLiveChat',
|
||||||
|
components: {
|
||||||
|
'ft-loader': FtLoader,
|
||||||
|
'ft-card': FtCard,
|
||||||
|
'ft-button': FtButton,
|
||||||
|
'ft-list-video': FtListVideo
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
videoId: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
channelName: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
liveChat: null,
|
||||||
|
isLoading: true,
|
||||||
|
hasError: false,
|
||||||
|
hasEnded: false,
|
||||||
|
showEnableChat: false,
|
||||||
|
errorMessage: '',
|
||||||
|
stayAtBottom: true,
|
||||||
|
showSuperChat: false,
|
||||||
|
showScrollToBottom: false,
|
||||||
|
comments: [],
|
||||||
|
superChatComments: [],
|
||||||
|
superChat: {
|
||||||
|
author: {
|
||||||
|
name: '',
|
||||||
|
thumbnail: ''
|
||||||
|
},
|
||||||
|
message: [
|
||||||
|
''
|
||||||
|
],
|
||||||
|
superChat: {
|
||||||
|
amount: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
usingElectron: function () {
|
||||||
|
return this.$store.getters.getUsingElectron
|
||||||
|
},
|
||||||
|
|
||||||
|
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'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created: function () {
|
||||||
|
if (!this.usingElectron) {
|
||||||
|
this.hasError = true
|
||||||
|
this.errorMessage = 'Live Chat is currently not supported in this build.'
|
||||||
|
} else {
|
||||||
|
switch (this.backendPreference) {
|
||||||
|
case 'local':
|
||||||
|
console.log('Getting Chat')
|
||||||
|
this.getLiveChatLocal()
|
||||||
|
break
|
||||||
|
case 'invidious':
|
||||||
|
if (this.backendFallback) {
|
||||||
|
this.getLiveChatLocal()
|
||||||
|
} else {
|
||||||
|
this.hasError = true
|
||||||
|
this.errorMessage = 'Live Chat is currently not supported with the Invidious API. A direct connection to YouTube is required.'
|
||||||
|
this.showEnableChat = true
|
||||||
|
this.isLoading = false
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
enableLiveChat: function () {
|
||||||
|
this.hasError = false
|
||||||
|
this.showEnableChat = false
|
||||||
|
this.isLoading = true
|
||||||
|
this.getLiveChatLocal()
|
||||||
|
},
|
||||||
|
|
||||||
|
getLiveChatLocal: function () {
|
||||||
|
this.liveChat = new LiveChat({ liveId: this.videoId })
|
||||||
|
|
||||||
|
this.isLoading = false
|
||||||
|
|
||||||
|
this.liveChat.on('start', (liveId) => {
|
||||||
|
console.log('Live chat is enabled')
|
||||||
|
this.isLoading = false
|
||||||
|
})
|
||||||
|
|
||||||
|
this.liveChat.on('end', (reason) => {
|
||||||
|
console.log('Live chat has ended')
|
||||||
|
console.log(reason)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.liveChat.on('error', (err) => {
|
||||||
|
this.hasError = true
|
||||||
|
this.errorMessage = err
|
||||||
|
this.showEnableChat = false
|
||||||
|
})
|
||||||
|
|
||||||
|
this.liveChat.on('comment', (comment) => {
|
||||||
|
this.parseLiveChatComment(comment)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.liveChat.start()
|
||||||
|
},
|
||||||
|
|
||||||
|
parseLiveChatComment: function (comment) {
|
||||||
|
if (this.hasEnded) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
comment.messageHtml = ''
|
||||||
|
|
||||||
|
comment.message.forEach((text) => {
|
||||||
|
comment.messageHtml = comment.messageHtml + text.text
|
||||||
|
})
|
||||||
|
|
||||||
|
comment.messageHtml = autolinker.link(comment.messageHtml)
|
||||||
|
|
||||||
|
const liveChatComments = $('.liveChatComments')
|
||||||
|
|
||||||
|
if (typeof (liveChatComments.get(0)) === 'undefined' && this.comments.length !== 0) {
|
||||||
|
this.liveChat.stop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.comments.push(comment)
|
||||||
|
|
||||||
|
if (typeof (comment.superchat) !== 'undefined') {
|
||||||
|
this.$store.dispatch('getRandomColorClass').then((data) => {
|
||||||
|
comment.superchat.colorClass = data
|
||||||
|
|
||||||
|
this.superChatComments.unshift(comment)
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.removeFromSuperChat(comment.id)
|
||||||
|
}, 120000)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comment.author.name[0] === 'Ge' || comment.author.name[0] === 'Ne') {
|
||||||
|
this.$store.dispatch('getRandomColorClass').then((data) => {
|
||||||
|
comment.superChat = {
|
||||||
|
amount: '$5.00',
|
||||||
|
colorClass: data
|
||||||
|
}
|
||||||
|
|
||||||
|
this.superChatComments.unshift(comment)
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.removeFromSuperChat(comment.id)
|
||||||
|
}, 120000)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.stayAtBottom) {
|
||||||
|
liveChatComments.animate({ scrollTop: liveChatComments.prop('scrollHeight') })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
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) {
|
||||||
|
const liveChatComments = $('.liveChatComments').get(0)
|
||||||
|
const scrollTop = liveChatComments.scrollTop
|
||||||
|
const scrollHeight = liveChatComments.scrollHeight
|
||||||
|
const clientHeight = liveChatComments.clientHeight
|
||||||
|
if (event.wheelDelta >= 0 && this.stayAtBottom) {
|
||||||
|
$('.liveChatComments').data('animating', 0)
|
||||||
|
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 () {
|
||||||
|
const liveChatComments = $('.liveChatComments')
|
||||||
|
liveChatComments.animate({ scrollTop: liveChatComments.prop('scrollHeight') })
|
||||||
|
this.stayAtBottom = true
|
||||||
|
this.showScrollToBottom = false
|
||||||
|
},
|
||||||
|
|
||||||
|
preventDefault: function (event) {
|
||||||
|
event.stopPropagation()
|
||||||
|
event.preventDefault()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeRouteLeave: function () {
|
||||||
|
this.liveChat.stop()
|
||||||
|
this.hasEnded = true
|
||||||
|
}
|
||||||
|
})
|
|
@ -0,0 +1,198 @@
|
||||||
|
<template>
|
||||||
|
<ft-card class="relative">
|
||||||
|
<ft-loader
|
||||||
|
v-if="isLoading"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-else-if="hasError"
|
||||||
|
class="messageContainer"
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
class="message"
|
||||||
|
>
|
||||||
|
{{ errorMessage }}
|
||||||
|
</p>
|
||||||
|
<font-awesome-icon
|
||||||
|
icon="exclamation-circle"
|
||||||
|
class="errorIcon"
|
||||||
|
/>
|
||||||
|
<ft-button
|
||||||
|
label="Enable Live Chat"
|
||||||
|
class="enableLiveChat"
|
||||||
|
@click="enableLiveChat"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else-if="comments.length === 0"
|
||||||
|
class="messageContainer"
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
class="message"
|
||||||
|
>
|
||||||
|
Live chat is enabled. Chat messages will appear here once sent.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="relative"
|
||||||
|
>
|
||||||
|
<h4>Live Chat</h4>
|
||||||
|
<div
|
||||||
|
v-if="superChatComments.length > 0"
|
||||||
|
class="superChatComments"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="(comment, index) in superChatComments"
|
||||||
|
:key="index"
|
||||||
|
:style="{ backgroundColor: 'var(--primary-color)' }"
|
||||||
|
class="superChat"
|
||||||
|
:class="comment.superchat.colorClass"
|
||||||
|
@click="showSuperChatComment(comment)"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
:src="comment.author.thumbnail.url"
|
||||||
|
class="channelThumbnail"
|
||||||
|
/>
|
||||||
|
<p
|
||||||
|
class="superChatContent"
|
||||||
|
:style="{ color: 'var(--text-with-main-color)' }"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="donationAmount"
|
||||||
|
>
|
||||||
|
{{ comment.superchat.amount }}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="openedSuperChat"
|
||||||
|
v-if="showSuperChat"
|
||||||
|
:class="superChat.superchat.colorClass"
|
||||||
|
@click="showSuperChat = false"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="superChatMessage"
|
||||||
|
@click="e => preventDefault(e)"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="upperSuperChatMessage"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
:src="superChat.author.thumbnail.url"
|
||||||
|
class="channelThumbnail"
|
||||||
|
/>
|
||||||
|
<p
|
||||||
|
class="channelName"
|
||||||
|
>
|
||||||
|
{{ superChat.author.name }}
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
class="donationAmount"
|
||||||
|
>
|
||||||
|
{{ superChat.superchat.amount }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="chatMessage"
|
||||||
|
v-if="superChat.message.length > 0"
|
||||||
|
v-html="comment.messageHtml"
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="liveChatComments"
|
||||||
|
:style="{ height: chatHeight }"
|
||||||
|
@mousewheel="e => onScroll(e)"
|
||||||
|
>
|
||||||
|
<div v-for="(comment, index) in comments"
|
||||||
|
:key="index"
|
||||||
|
class="comment">
|
||||||
|
<div
|
||||||
|
v-if="typeof (comment.superchat) !== 'undefined'"
|
||||||
|
class="superChatMessage"
|
||||||
|
:class="comment.superchat.colorClass"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="upperSuperChatMessage"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
:src="comment.author.thumbnail.url"
|
||||||
|
class="channelThumbnail"
|
||||||
|
/>
|
||||||
|
<p
|
||||||
|
class="channelName"
|
||||||
|
>
|
||||||
|
{{ comment.author.name }}
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
class="donationAmount"
|
||||||
|
>
|
||||||
|
{{ comment.superchat.amount }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="chatMessage"
|
||||||
|
v-if="comment.message.length > 0"
|
||||||
|
v-html="comment.messageHtml"
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
:src="comment.author.thumbnail.url"
|
||||||
|
class="channelThumbnail"
|
||||||
|
/>
|
||||||
|
<p
|
||||||
|
class="chatContent"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="channelName"
|
||||||
|
:class="{
|
||||||
|
member: typeof (comment.author.badge) !== 'undefined' || comment.membership,
|
||||||
|
moderator: comment.isOwner,
|
||||||
|
owner: comment.author.name === channelName
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ comment.author.name }}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
v-if="typeof (comment.author.badge) !== 'undefined'"
|
||||||
|
class="badge"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
:src="comment.author.badge.thumbnail.url"
|
||||||
|
:alt="comment.author.badge.thumbnail.alt"
|
||||||
|
:title="comment.author.badge.thumbnail.alt"
|
||||||
|
class="badgeImage"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="chatMessage"
|
||||||
|
v-if="comment.message.length > 0"
|
||||||
|
v-html="comment.messageHtml"
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="showScrollToBottom"
|
||||||
|
class="scrollToBottom"
|
||||||
|
@click="scrollToBottom"
|
||||||
|
>
|
||||||
|
<font-awesome-icon
|
||||||
|
class="icon"
|
||||||
|
icon="arrow-down"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ft-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./watch-video-live-chat.js" />
|
||||||
|
<style scoped src="./watch-video-live-chat.css" />
|
|
@ -8,7 +8,25 @@ const state = {
|
||||||
time: '',
|
time: '',
|
||||||
type: 'all',
|
type: 'all',
|
||||||
duration: ''
|
duration: ''
|
||||||
}
|
},
|
||||||
|
colorClasses: [
|
||||||
|
'mainRed',
|
||||||
|
'mainPink',
|
||||||
|
'mainPurple',
|
||||||
|
'mainDeepPurple',
|
||||||
|
'mainIndigo',
|
||||||
|
'mainBlue',
|
||||||
|
'mainLightBlue',
|
||||||
|
'mainCyan',
|
||||||
|
'mainTeal',
|
||||||
|
'mainGreen',
|
||||||
|
'mainLightGreen',
|
||||||
|
'mainLime',
|
||||||
|
'mainYellow',
|
||||||
|
'mainAmber',
|
||||||
|
'mainOrange',
|
||||||
|
'mainDeepOrange'
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
const getters = {
|
const getters = {
|
||||||
|
@ -29,7 +47,12 @@ const getters = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const actions = {}
|
const actions = {
|
||||||
|
getRandomColorClass () {
|
||||||
|
const randomInt = Math.floor(Math.random() * state.colorClasses.length)
|
||||||
|
return state.colorClasses[randomInt]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const mutations = {
|
const mutations = {
|
||||||
toggleSideNav (state) {
|
toggleSideNav (state) {
|
||||||
|
|
|
@ -107,6 +107,7 @@
|
||||||
.watchVideoPlaylist {
|
.watchVideoPlaylist {
|
||||||
float: none;
|
float: none;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
margin-bottom: 10px;
|
||||||
width: 85%;
|
width: 85%;
|
||||||
max-width: none;
|
max-width: none;
|
||||||
position: static;
|
position: static;
|
||||||
|
|
|
@ -8,6 +8,7 @@ import FtVideoPlayer from '../../components/ft-video-player/ft-video-player.vue'
|
||||||
import WatchVideoInfo from '../../components/watch-video-info/watch-video-info.vue'
|
import WatchVideoInfo from '../../components/watch-video-info/watch-video-info.vue'
|
||||||
import WatchVideoDescription from '../../components/watch-video-description/watch-video-description.vue'
|
import WatchVideoDescription from '../../components/watch-video-description/watch-video-description.vue'
|
||||||
import WatchVideoComments from '../../components/watch-video-comments/watch-video-comments.vue'
|
import WatchVideoComments from '../../components/watch-video-comments/watch-video-comments.vue'
|
||||||
|
import WatchVideoLiveChat from '../../components/watch-video-live-chat/watch-video-live-chat.vue'
|
||||||
import WatchVideoPlaylist from '../../components/watch-video-playlist/watch-video-playlist.vue'
|
import WatchVideoPlaylist from '../../components/watch-video-playlist/watch-video-playlist.vue'
|
||||||
import WatchVideoRecommendations from '../../components/watch-video-recommendations/watch-video-recommendations.vue'
|
import WatchVideoRecommendations from '../../components/watch-video-recommendations/watch-video-recommendations.vue'
|
||||||
|
|
||||||
|
@ -21,6 +22,7 @@ export default Vue.extend({
|
||||||
'watch-video-info': WatchVideoInfo,
|
'watch-video-info': WatchVideoInfo,
|
||||||
'watch-video-description': WatchVideoDescription,
|
'watch-video-description': WatchVideoDescription,
|
||||||
'watch-video-comments': WatchVideoComments,
|
'watch-video-comments': WatchVideoComments,
|
||||||
|
'watch-video-live-chat': WatchVideoLiveChat,
|
||||||
'watch-video-playlist': WatchVideoPlaylist,
|
'watch-video-playlist': WatchVideoPlaylist,
|
||||||
'watch-video-recommendations': WatchVideoRecommendations,
|
'watch-video-recommendations': WatchVideoRecommendations,
|
||||||
},
|
},
|
||||||
|
@ -185,16 +187,21 @@ export default Vue.extend({
|
||||||
this.isLive = result.player_response.videoDetails.isLive
|
this.isLive = result.player_response.videoDetails.isLive
|
||||||
|
|
||||||
if (this.isLive) {
|
if (this.isLive) {
|
||||||
this.showLegacyPlayer = false
|
this.showLegacyPlayer = true
|
||||||
this.showDashPlayer = true
|
this.showDashPlayer = false
|
||||||
this.videoSourceList = [
|
|
||||||
{
|
this.videoSourceList = result.formats.filter((format) => {
|
||||||
url: 'https://invidious.snopyta.org/api/manifest/dash/id/EEIk7gwjgIM',
|
if (typeof (format.mimeType) !== 'undefined') {
|
||||||
type: 'application/dash+xml',
|
return format.mimeType.includes('video/ts')
|
||||||
|
}
|
||||||
|
}).map((format) => {
|
||||||
|
return {
|
||||||
|
url: format.url,
|
||||||
|
type: 'application/x-mpegURL',
|
||||||
label: 'Dash',
|
label: 'Dash',
|
||||||
qualityLabel: 'Auto'
|
qualityLabel: format.qualityLabel
|
||||||
},
|
}
|
||||||
]
|
})
|
||||||
} else {
|
} else {
|
||||||
this.videoSourceList = result.player_response.streamingData.formats
|
this.videoSourceList = result.player_response.streamingData.formats
|
||||||
}
|
}
|
||||||
|
@ -271,6 +278,7 @@ export default Vue.extend({
|
||||||
this.videoPublished = result.published * 1000
|
this.videoPublished = result.published * 1000
|
||||||
this.videoDescriptionHtml = result.descriptionHtml
|
this.videoDescriptionHtml = result.descriptionHtml
|
||||||
this.recommendedVideos = result.recommendedVideos
|
this.recommendedVideos = result.recommendedVideos
|
||||||
|
this.isLive = result.liveNow
|
||||||
this.captionSourceList = result.captions.map(caption => {
|
this.captionSourceList = result.captions.map(caption => {
|
||||||
caption.url = this.invidiousInstance + caption.url
|
caption.url = this.invidiousInstance + caption.url
|
||||||
caption.type = ''
|
caption.type = ''
|
||||||
|
@ -278,7 +286,35 @@ export default Vue.extend({
|
||||||
return caption
|
return caption
|
||||||
})
|
})
|
||||||
|
|
||||||
if (this.forceLocalBackendForLegacy) {
|
if (this.isLive) {
|
||||||
|
this.showLegacyPlayer = true
|
||||||
|
this.showDashPlayer = false
|
||||||
|
this.activeFormat = 'legacy'
|
||||||
|
|
||||||
|
this.videoSourceList = [
|
||||||
|
{
|
||||||
|
url: result.hlsUrl,
|
||||||
|
type: 'application/x-mpegURL',
|
||||||
|
label: 'Dash',
|
||||||
|
qualityLabel: 'Live'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// Grabs the adaptive formats from Invidious. Might be worth making these work.
|
||||||
|
// The type likely needs to be changed in order for these to be played properly.
|
||||||
|
// this.videoSourceList = result.adaptiveFormats.filter((format) => {
|
||||||
|
// if (typeof (format.type) !== 'undefined') {
|
||||||
|
// return format.type.includes('video/mp4')
|
||||||
|
// }
|
||||||
|
// }).map((format) => {
|
||||||
|
// return {
|
||||||
|
// url: format.url,
|
||||||
|
// type: 'application/x-mpegURL',
|
||||||
|
// label: 'Dash',
|
||||||
|
// qualityLabel: format.qualityLabel
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
} else if (this.forceLocalBackendForLegacy) {
|
||||||
this.getLegacyFormats()
|
this.getLegacyFormats()
|
||||||
} else {
|
} else {
|
||||||
this.videoSourceList = result.formatStreams.reverse()
|
this.videoSourceList = result.formatStreams.reverse()
|
||||||
|
@ -302,8 +338,6 @@ export default Vue.extend({
|
||||||
|
|
||||||
checkIfPlaylist: function () {
|
checkIfPlaylist: function () {
|
||||||
if (typeof (this.$route.query) !== 'undefined') {
|
if (typeof (this.$route.query) !== 'undefined') {
|
||||||
console.log('defined')
|
|
||||||
console.log(this.$route.query)
|
|
||||||
this.playlistId = this.$route.query.playlistId
|
this.playlistId = this.$route.query.playlistId
|
||||||
|
|
||||||
if (typeof (this.playlistId) !== 'undefined') {
|
if (typeof (this.playlistId) !== 'undefined') {
|
||||||
|
@ -325,7 +359,7 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
enableDashFormat: function () {
|
enableDashFormat: function () {
|
||||||
if (this.activeFormat === 'dash') {
|
if (this.activeFormat === 'dash' || this.isLive) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -361,6 +395,10 @@ export default Vue.extend({
|
||||||
|
|
||||||
handleVideoError: function(error) {
|
handleVideoError: function(error) {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
|
if (this.isLive) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (error.code === 4) {
|
if (error.code === 4) {
|
||||||
if (this.activeFormat === 'dash') {
|
if (this.activeFormat === 'dash') {
|
||||||
console.log(
|
console.log(
|
||||||
|
|
|
@ -41,11 +41,18 @@
|
||||||
:class="{ theatreWatchVideo: useTheatreMode }"
|
:class="{ theatreWatchVideo: useTheatreMode }"
|
||||||
/>
|
/>
|
||||||
<watch-video-comments
|
<watch-video-comments
|
||||||
v-if="!isLoading"
|
v-if="!isLoading && !isLive"
|
||||||
:id="videoId"
|
:id="videoId"
|
||||||
class="watchVideo"
|
class="watchVideo"
|
||||||
:class="{ theatreWatchVideo: useTheatreMode }"
|
:class="{ theatreWatchVideo: useTheatreMode }"
|
||||||
/>
|
/>
|
||||||
|
<watch-video-live-chat
|
||||||
|
v-if="!isLoading && isLive"
|
||||||
|
:video-id="videoId"
|
||||||
|
:channel-name="channelName"
|
||||||
|
class="watchVideoSideBar watchVideoPlaylist"
|
||||||
|
:class="{ theatrePlaylist: useTheatreMode }"
|
||||||
|
/>
|
||||||
<watch-video-playlist
|
<watch-video-playlist
|
||||||
v-if="watchingPlaylist"
|
v-if="watchingPlaylist"
|
||||||
v-show="!isLoading"
|
v-show="!isLoading"
|
||||||
|
@ -61,8 +68,8 @@
|
||||||
class="watchVideoSideBar watchVideoRecommendations"
|
class="watchVideoSideBar watchVideoRecommendations"
|
||||||
:class="{
|
:class="{
|
||||||
theatreRecommendations: useTheatreMode,
|
theatreRecommendations: useTheatreMode,
|
||||||
watchVideoRecommendationsLowerCard: watchingPlaylist,
|
watchVideoRecommendationsLowerCard: watchingPlaylist || isLive,
|
||||||
watchVideoRecommendationsNoCard: !watchingPlaylist
|
watchVideoRecommendationsNoCard: !watchingPlaylist || !isLive
|
||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in New Issue