0
0
Fork 0
mirror of https://github.com/VueTubeApp/VueTube synced 2024-12-01 23:47:29 +00:00
VueTube/NUXT/pages/watch.vue

659 lines
18 KiB
Vue
Raw Normal View History

2022-02-24 22:29:34 +00:00
<template>
2023-03-30 01:09:11 +00:00
<div id="watch-body" class="background">
2022-04-18 05:58:59 +00:00
<div id="player-container">
2022-05-29 18:13:09 +00:00
<!-- // TODO: move component to default.vue -->
<!-- // TODO: pass sources through vuex instead of props -->
<player
v-if="sources.length > 0 && video.title && video.channelName"
ref="player"
:video="video"
:sources="sources"
:recommends="recommends"
:disabled="saveDialog"
/>
</div>
2022-04-18 05:58:59 +00:00
<div
id="content-container"
:class="{
2022-04-18 05:58:59 +00:00
'overflow-y-auto': !showComments,
'overflow-y-hidden': showComments,
}"
>
<v-card v-if="loaded" class="background rounded-0" flat>
<div
v-ripple
2022-07-07 01:53:15 +00:00
class="d-flex justify-space-between align-start px-4 pt-4"
@click="showMore = !showMore"
>
2022-04-24 04:01:52 +00:00
<div class="d-flex flex-column">
<v-card-title
v-emoji
class="pa-0 text-wrap"
style="font-size: 0.95rem; line-height: 1.15rem"
2022-04-24 04:01:52 +00:00
v-text="video.title"
/>
<v-card-text
style="font-size: 0.75rem"
class="background--text pa-0"
:class="
$vuetify.theme.dark ? 'text--lighten-4' : 'text--darken-4'
"
>
2022-05-05 23:09:28 +00:00
<div>
2022-04-24 04:01:52 +00:00
<template
v-for="text in video.metadata.contents.find(
(content) => content.slimVideoInformationRenderer
).slimVideoInformationRenderer.collapsedSubtitle.runs"
>{{ text.text }}
</template>
</div>
</v-card-text>
</div>
<v-icon v-if="showMore" class="ml-4">mdi-chevron-up</v-icon>
<v-icon v-else class="ml-4">mdi-chevron-down</v-icon>
2022-04-15 03:14:52 +00:00
</div>
<div
class="d-flex justify-space-around"
:class="
$store.state.tweaks.roundWatch && $store.state.tweaks.roundTweak > 0
? $vuetify.theme.dark
? 'background lighten-1'
: 'background darken-1'
: ''
"
:style="{
borderRadius: $store.state.tweaks.roundWatch
? `${$store.state.tweaks.roundTweak / 2}rem`
: '0',
margin:
$store.state.tweaks.roundWatch &&
$store.state.tweaks.roundTweak > 0
? '1rem'
: '0',
}"
>
<v-btn
v-for="(item, index) in interactions"
:key="index"
text
fab
2022-07-20 04:11:28 +00:00
class="vertical-button"
elevation="0"
style="
width: 4.2rem !important;
height: 4.2rem !important;
text-transform: none !important;
"
:disabled="item.disabled"
@click="callMethodByName(item.actionName)"
>
<v-icon v-text="item.icon" />
<div
class="mt-1"
style="font-size: 0.6rem"
v-text="item.value || item.name"
/>
2022-04-24 04:01:52 +00:00
</v-btn>
<!-- End Scrolling Div For Interactions --->
<!-- <hr /> -->
</div>
<!-- <v-bottom-sheet
2022-04-15 03:14:52 +00:00
v-model="showMore"
color="background"
style="z-index: 9999999"
>
<v-sheet style="padding: 12px">
<v-btn block @click="showMore = !showMore"
><v-icon>mdi-chevron-down</v-icon></v-btn
><br />
<slim-video-description-renderer
class="scroll-y"
:render="video.renderedData.description"
/>
</v-sheet>
</v-bottom-sheet> -->
2022-04-24 04:01:52 +00:00
<!-- <v-bottom-sheet v-model="share" color="background" style="z-index: 9999999">
2022-04-15 03:14:52 +00:00
<v-sheet style="padding: 1em">
<div class="scroll-y">
{{ response.renderedData.description }}
</div>
</v-sheet>
</v-bottom-sheet> -->
2022-04-24 04:01:52 +00:00
</v-card>
2022-05-14 00:25:48 +00:00
<v-divider
v-if="
!$store.state.tweaks.roundTweak || !$store.state.tweaks.roundWatch
"
/>
2022-04-15 03:14:52 +00:00
2022-04-24 04:01:52 +00:00
<!-- Channel Bar -->
<div v-if="loaded">
2022-04-24 04:01:52 +00:00
<v-card
2022-05-05 23:39:41 +00:00
flat
2022-05-14 00:25:48 +00:00
class="channel-section py-2 px-3 background"
:class="
$store.state.tweaks.roundWatch && $store.state.tweaks.roundTweak > 0
? $vuetify.theme.dark
? 'background lighten-1'
: 'background darken-1'
: ''
"
2022-05-14 04:16:15 +00:00
to="/channel"
2022-05-14 00:25:48 +00:00
:style="{
borderRadius: $store.state.tweaks.roundWatch
? `${$store.state.tweaks.roundTweak / 2}rem`
: '0',
margin:
$store.state.tweaks.roundWatch &&
$store.state.tweaks.roundTweak > 0
? '1rem'
: '0',
}"
2022-05-14 04:16:15 +00:00
@click="$store.dispatch('channel/fetchChannel', video.channelUrl)"
2022-04-24 04:01:52 +00:00
>
<div id="details">
<div class="avatar-link mr-3">
<v-img class="avatar-thumbnail" :src="video.channelImg" />
</div>
2023-03-30 01:09:11 +00:00
<div v-emoji class="channel-byline">
2022-04-24 04:01:52 +00:00
<div class="channel-name" v-text="video.channelName" />
<div
class="caption background--text"
:class="
$vuetify.theme.dark ? 'text--lighten-4' : 'text--darken-4'
"
2022-12-27 04:46:27 +00:00
v-text="video.channelSubs + ' subscribers'"
2022-04-24 04:01:52 +00:00
/>
</div>
</div>
2022-05-05 23:09:28 +00:00
<div class="primary--text" style="text-transform: uppercase">
2022-04-24 04:01:52 +00:00
subscribe
</div>
2022-04-24 04:01:52 +00:00
</v-card>
</div>
2022-05-14 00:25:48 +00:00
<v-divider
v-if="
!$store.state.tweaks.roundTweak || !$store.state.tweaks.roundWatch
"
/>
2022-04-24 04:01:52 +00:00
<!-- Description -->
<div v-if="showMore">
2022-06-07 17:16:26 +00:00
<div class="scroll-y ma-4 pt-1">
2022-04-24 04:01:52 +00:00
<slim-video-description-renderer
:render="video.renderedData.description"
/>
</div>
</div>
2022-05-14 00:25:48 +00:00
<v-divider
v-if="
showMore &&
(!$store.state.tweaks.roundTweak || !$store.state.tweaks.roundWatch)
2022-05-14 00:25:48 +00:00
"
/>
2022-04-24 04:01:52 +00:00
<!-- Comments -->
2022-05-14 00:25:48 +00:00
<div v-if="loaded && video.commentData" @click="toggleComment">
<v-card
v-ripple
flat
tile
class="comment-renderer px-3 background"
:class="
$store.state.tweaks.roundWatch && $store.state.tweaks.roundTweak > 0
? $vuetify.theme.dark
? 'background lighten-1'
: 'background darken-1'
: ''
"
:style="{
borderRadius: $store.state.tweaks.roundWatch
? `${$store.state.tweaks.roundTweak / 2}rem !important`
: '0',
margin:
$store.state.tweaks.roundWatch &&
$store.state.tweaks.roundTweak > 0
? '1rem'
: '0',
}"
>
2022-05-04 03:29:29 +00:00
<v-card-text class="comment-count keep-spaces px-0">
2022-04-24 04:01:52 +00:00
<template v-for="text in video.commentData.headerText.runs">
<template v-if="text.bold">
<strong :key="text.text">{{ text.text }}</strong>
</template>
<template v-else>{{ text.text }}</template>
</template>
2022-05-02 04:42:56 +00:00
</v-card-text>
2022-05-04 03:29:29 +00:00
<v-icon v-if="showComments" dense>mdi-unfold-less-horizontal</v-icon>
<v-icon v-else dense>mdi-unfold-more-horizontal</v-icon>
2022-04-24 04:01:52 +00:00
</v-card>
</div>
2022-04-09 06:20:51 +00:00
2022-05-14 00:25:48 +00:00
<v-divider
v-if="
!$store.state.tweaks.roundTweak || !$store.state.tweaks.roundWatch
"
/>
2022-12-27 16:44:51 +00:00
<div
v-if="loaded && video.commentData"
2022-12-27 16:44:51 +00:00
:class="showComments ? 'comments-open' : ''"
class="scroll-y comments"
2022-04-24 04:01:52 +00:00
>
<mainCommentRenderer
v-model="showComments"
:comment-data="video.commentData"
:default-continuation="video.commentContinuation"
2022-04-24 04:01:52 +00:00
></mainCommentRenderer>
2022-12-27 16:44:51 +00:00
</div>
2022-04-24 04:01:52 +00:00
<!-- <swipeable-bottom-sheet
:v-model="showComments"
style="z-index: 9999999"
></swipeable-bottom-sheet> -->
2022-04-13 12:54:06 +00:00
2022-04-24 04:01:52 +00:00
<!-- Related Videos -->
<div v-if="!loaded">
2022-04-24 04:01:52 +00:00
<v-skeleton-loader
type="list-item-two-line, actions, divider, list-item-avatar, divider, list-item-three-line"
/>
<vid-load-renderer :count="5" />
</div>
<item-section-renderer
v-else
:render="recommends"
:style="{
marginTop: $store.state.tweaks.roundTweak > 0 ? '1rem' : '0',
}"
/>
</div>
<v-dialog v-model="saveDialog" width="500">
<v-card
class="rounded-lg"
:class="
$vuetify.theme.dark ? 'background lighten-1' : 'background darken-1'
"
>
<v-card-title class="text-h5">Save To Playlist</v-card-title>
<v-spacer></v-spacer>
<v-checkbox
v-for="(playlist, index) in playlists"
:key="index"
v-model="playlistsCheckbox[index]"
class="mx-5"
:label="playlist.name"
@change="updatePlaylist($event, index)"
/>
<v-divider />
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="primary" text @click="saveDialog = false"> Done </v-btn>
</v-card-actions>
</v-card></v-dialog
>
</div>
2022-02-24 22:29:34 +00:00
</template>
<script>
2022-03-22 05:47:28 +00:00
import { Share } from "@capacitor/share";
import { getCpn } from "~/plugins/utils";
2022-12-27 16:44:51 +00:00
import player from "~/components/Player/index.vue";
2022-05-28 01:36:26 +00:00
import VidLoadRenderer from "~/components/vidLoadRenderer.vue";
import ItemSectionRenderer from "~/components/SectionRenderers/itemSectionRenderer.vue";
2022-04-19 14:03:46 +00:00
import mainCommentRenderer from "~/components/Comments/mainCommentRenderer.vue";
2022-05-28 01:36:26 +00:00
import SlimVideoDescriptionRenderer from "~/components/UtilRenderers/slimVideoDescriptionRenderer.vue";
2022-03-22 05:47:28 +00:00
2022-05-04 03:29:29 +00:00
import backType from "~/plugins/classes/backType";
2022-02-24 22:29:34 +00:00
export default {
components: {
2022-05-28 01:36:26 +00:00
player,
VidLoadRenderer,
ItemSectionRenderer,
2022-04-19 14:03:46 +00:00
mainCommentRenderer,
2022-05-28 01:36:26 +00:00
SlimVideoDescriptionRenderer,
},
2022-04-15 03:14:52 +00:00
layout: "empty",
// transition(to) { // TODO: fix layout switching
// return to.name == "watch"
// ? { name: "slide-up", mode: "" }
// : { name: "slide-down", mode: "" };
// },
data: function () {
return this.initializeState();
},
computed: {
playlists() {
return this.$store.state.playlist.playlists;
},
},
2022-03-25 03:25:51 +00:00
watch: {
// Watch for change in the route query string (in this case, ?v=xxxxxxxx to ?v=yyyyyyyy)
$route: {
deep: true,
handler(newRt, oldRt) {
if (newRt.query.v && newRt.query.v != oldRt.query.v) {
2022-03-25 03:25:51 +00:00
// Exit fullscreen if currently in fullscreen
// if (this.$refs.player) this.$refs.player.webkitExitFullscreen();
2022-03-25 03:25:51 +00:00
// Reset player and run getVideo function again
// this.startTime = Math.floor(Date.now() / 1000);
// this.getVideo();
clearInterval(this.interval);
Object.assign(this.$data, this.initializeState());
this.mountedInit();
2022-03-25 03:25:51 +00:00
}
},
},
},
2022-04-19 14:03:46 +00:00
2022-03-13 23:21:41 +00:00
mounted() {
this.mountedInit();
2022-05-02 04:01:48 +00:00
this.$vuetube.resetBackActions();
},
2022-04-19 14:03:46 +00:00
beforeDestroy() {
clearInterval(this.interval);
},
2022-04-19 14:03:46 +00:00
methods: {
getVideo() {
this.loaded = false;
this.$youtube.getVid(this.$route.query.v).then((result) => {
// TODO: sourt "tiny" (no qualityLabel) as audio and rest as video
this.sources = result.availableResolutionsAdaptive;
2022-05-28 01:36:26 +00:00
console.log("Video info data", result);
this.video = result;
//--- Content Stuff ---//
2023-01-06 19:12:55 +00:00
// NOTE: extractor likes are broken, using RYD likes instead
// this.likes = result.metadata.likes.toLocaleString();
// this.interactions[0].value = result.metadata.likes.toLocaleString();
this.loaded = true;
this.recommends = result.renderedData.recommendations;
console.log("recommendations:", this.recommends);
2023-03-30 01:09:11 +00:00
// Store To History
this.$store.commit("history/addHistory", {
id: this.video.id,
title: this.video.title,
channel: this.video.channelName,
});
this.playlistsCheckbox = this.playlists.map(
(playlist) =>
playlist.videos.findIndex(
(playlistVideo) => playlistVideo.id === this.video.id
) !== -1
);
//--- API WatchTime call ---//
if (this.$store.state.watchTelemetry) {
this.playbackTracking = result.playbackTracking;
this.st = 0;
this.cpn = getCpn();
this.initWatchTime().then(() => {
this.sendWatchTime();
this.interval = setInterval(this.sendWatchTime, 60000);
});
}
});
2022-03-19 06:18:07 +00:00
this.$youtube.getReturnYoutubeDislike(this.$route.query.v, (data) => {
2023-01-06 19:12:55 +00:00
this.likes = data.likes.toLocaleString();
this.dislikes = data.dislikes.toLocaleString();
2023-01-06 19:12:55 +00:00
this.interactions[0].value = data.likes.toLocaleString();
this.interactions[1].value = data.dislikes.toLocaleString();
});
},
2022-03-22 05:47:28 +00:00
callMethodByName(name) {
// Helper function needed because of issues when directly calling method
// using item.action in the v-for loop
this[name]();
},
async share() {
// this.share = !this.share;
await Share.share({
title: this.video.title,
text: this.video.title,
url:
"https://youtu.be/" +
this.$route.query.v +
"?t=" +
Math.round(this.$refs.player.getPlayer().currentTime) +
"s",
dialogTitle: "Share video",
2022-03-22 05:47:28 +00:00
});
},
sendWatchTime() {
const player = this.$refs.player.getPlayer();
const rt = Math.floor(Date.now() / 1000) - this.startTime;
const params = {
cpn: this.cpn,
rt: rt,
rti: rt,
rtn: rt,
cmt: player.currentTime,
et: player.currentTime,
st: this.st,
state: player.paused ? "paused" : "playing",
volume: 100,
muted: 0,
fmt: 396,
};
this.st = player.currentTime;
this.$youtube.saveApiStats(
params,
this.playbackTracking.videostatsWatchtimeUrl.baseUrl
);
},
async initWatchTime() {
await this.$youtube.saveApiStats(
{
cpn: this.cpn,
fmt: 243,
rtn: Math.floor(Date.now() / 1000) - this.startTime,
rt: Math.floor(Date.now() / 1000) - this.startTime,
muted: 0,
},
this.playbackTracking.videostatsPlaybackUrl.baseUrl
);
},
initializeState() {
return {
interactions: [
{
name: "Likes",
icon: "mdi-thumb-up-outline",
2022-05-18 03:19:22 +00:00
// action: this.like(),
actionName: "like",
value: this.likes,
disabled: true,
},
{
name: "Dislikes",
icon: "mdi-thumb-down-outline",
// action: this.dislike(),
actionName: "dislike",
value: this.dislikes,
disabled: true,
},
{
name: "Share",
icon: "mdi-share-outline",
// action: this.share(),
actionName: "share",
disabled: false,
},
2022-05-14 00:25:48 +00:00
{
name: "Save",
icon: "mdi-plus-box-multiple-outline",
// action: this.save()
actionName: "save",
disabled: false,
2022-05-14 00:25:48 +00:00
},
2022-05-21 04:20:02 +00:00
// {
// name: "Quality",
// icon: "mdi-high-definition",
// actionName: "quality",
// disabled: false,
// },
// {
// name: "Speed",
// icon: "mdi-speedometer",
// actionName: "speed",
// disabled: false,
// },
],
showMore: false,
2022-04-13 12:54:06 +00:00
showComments: false,
// share: false,
sources: [],
recommends: null,
loaded: false,
interval: null,
video: null,
2022-05-02 04:42:56 +00:00
backHierarchy: [],
saveDialog: false,
playlistsCheckbox: [],
};
},
mountedInit() {
this.startTime = Math.floor(Date.now() / 1000);
this.getVideo();
// Reset vertical scrolling
const scrollableList = document.querySelectorAll(".overflow-y-auto");
scrollableList.forEach((scrollable) => {
scrollable.scrollTo(0, 0);
});
},
2022-05-04 03:29:29 +00:00
// Toggle this.showComments to true or false. If it is true, then add the dismiss function to backStack.
toggleComment() {
2022-12-27 17:06:19 +00:00
document.getElementById("content-container").scrollTo(0, 0);
2022-05-04 03:29:29 +00:00
this.showComments = !this.showComments;
if (this.showComments) {
const dismissComment = new backType(
() => {
this.showComments = false;
},
() => {
return this.showComments;
}
);
this.$vuetube.addBackAction(dismissComment);
}
},
save() {
this.saveDialog = true;
},
updatePlaylist(event, index) {
if (event) {
this.$store.commit("playlist/addToPlaylist", {
video: {
id: this.video.id,
title: this.video.title,
channel: this.video.channelName,
},
index,
});
} else {
this.$store.commit("playlist/removeFromPlaylist", {
video: this.video,
playlistIndex: index,
});
}
},
2022-03-21 23:47:11 +00:00
},
};
2022-02-24 22:29:34 +00:00
</script>
<style>
2022-12-27 16:44:51 +00:00
.comments {
left: 0;
right: 0;
bottom: 0;
opacity: 0;
2022-12-27 17:06:19 +00:00
z-index: 2;
2022-12-27 16:44:51 +00:00
height: 100%;
max-height: 100%;
position: absolute;
2022-12-27 16:54:24 +00:00
pointer-events: none;
2022-12-27 16:44:51 +00:00
transform: translateY(100%);
transition: transform 0.3s ease-in-out, opacity 0.3s ease-in-out;
}
.comments-open {
transform: translatey(0);
2022-12-27 16:54:24 +00:00
pointer-events: auto;
2022-12-27 16:44:51 +00:00
opacity: 1;
}
#watch-body {
height: 100%;
max-height: 100vh;
overflow: hidden;
display: flex;
flex-direction: column;
}
2022-04-18 05:58:59 +00:00
#content-container {
height: 100%;
2022-04-18 05:58:59 +00:00
position: relative;
}
.vertical-button span.v-btn__content {
flex-direction: column;
justify-content: space-around;
}
.channel-section,
2022-04-09 06:20:51 +00:00
.comment-renderer {
display: flex;
align-items: center;
}
2022-04-09 06:20:51 +00:00
.channel-section #details,
.comment-renderer .comment-count {
flex-grow: 1;
display: flex;
align-items: center;
min-width: 0;
}
.channel-section .channel-byline {
min-width: 0;
}
.channel-section .avatar-thumbnail {
border-radius: 50%;
width: 35px;
height: 35px;
}
.channel-section .channel-name {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
2022-04-09 06:20:51 +00:00
.keep-spaces {
white-space: pre-wrap;
}
.v-card__title {
word-break: break-word;
}
</style>