feat: video watchtime now affects recommendations (I think)

This commit is contained in:
Alex 2022-04-02 02:04:52 +13:00
parent 142afabda2
commit 9d83a56aeb
6 changed files with 86 additions and 46 deletions

View File

@ -1,38 +1,35 @@
<template>
<div>
<video
controls
autoplay
:src="vidSrc"
width="100%"
@webkitfullscreenchange="handleFullscreenChange"
ref="player"
style="max-height: 50vh"
controls
autoplay
:src="vidSrc"
width="100%"
@webkitfullscreenchange="handleFullscreenChange"
ref="player"
style="max-height: 50vh"
/>
</div>
</template>
<script>
export default {
props: [
"vidSrc"
],
export default {
props: ["vidSrc"],
methods: {
handleFullscreenChange() {
if (document.fullscreenElement === this.$refs.player) {
this.$vuetube.statusBar.hide();
this.$vuetube.navigationBar.hide();
} else {
this.$vuetube.statusBar.show();
this.$vuetube.navigationBar.show();
}
},
}
}
methods: {
handleFullscreenChange() {
if (document.fullscreenElement === this.$refs.player) {
this.$vuetube.statusBar.hide();
this.$vuetube.navigationBar.hide();
} else {
this.$vuetube.statusBar.show();
this.$vuetube.navigationBar.show();
}
},
getPlayer() {
return this.$refs.player;
},
},
};
</script>

View File

@ -4,6 +4,7 @@
<videoPlayer
style="position: sticky; top: 0; z-index: 696969"
:vid-src="vidSrc"
ref="player"
/>
<!-- VueTube Player V1 -->
@ -95,6 +96,7 @@
import { Share } from "@capacitor/share";
import ShelfRenderer from "~/components/SectionRenderers/shelfRenderer.vue";
import VidLoadRenderer from "~/components/vidLoadRenderer.vue";
import { getCpn } from "~/plugins/utils";
import SlimVideoDescriptionRenderer from "~/components/UtilRenderers/slimVideoDescriptionRenderer.vue";
export default {
@ -135,6 +137,7 @@ export default {
views: null,
recommends: null,
loaded: false,
interval: null,
};
},
watch: {
@ -144,18 +147,24 @@ export default {
handler(newRt, oldRt) {
if (newRt.query.v != oldRt.query.v) {
// Exit fullscreen if currently in fullscreen
if (this.$refs.player) this.$refs.player.webkitExitFullscreen();
// if (this.$refs.player) this.$refs.player.webkitExitFullscreen();
// Reset player and run getVideo function again
this.vidSrc = "";
this.startTime = Math.floor(Date.now() / 1000);
clearInterval(this.interval);
this.getVideo();
}
},
},
},
mounted() {
this.$youtube.saveApiStats("detailpage", this.$route.query.v, "streamingstats")
this.startTime = Math.floor(Date.now() / 1000);
this.getVideo();
},
destroyed() {
clearInterval(this.interval);
},
methods: {
getVideo() {
this.likes = 100;
@ -186,6 +195,14 @@ export default {
this.recommends = result.renderedData.recommendations;
// .catch((error) => this.$logger("Watch", error, true));
console.log("recommendations:", this.recommends);
//--- API WatchTime call ---//
this.playbackTracking = result.playbackTracking;
this.cpn = getCpn();
this.initWatchTime().then(() => {
this.sendWatchTime();
this.interval = setInterval(this.sendWatchTime, 30000);
});
});
this.$youtube.getReturnYoutubeDislike(this.$route.query.v, (data) => {
@ -208,6 +225,29 @@ export default {
dialogTitle: "Share video",
});
},
sendWatchTime() {
const player = this.$refs.player.getPlayer();
this.$youtube.saveApiStats(
{
cpn: this.cpn,
rt: Math.floor(Date.now() / 1000) - this.startTime,
et: player.currentTime,
state: player.paused ? "paused" : "playing",
volume: 100,
},
this.playbackTracking.videostatsWatchtimeUrl.baseUrl
);
},
async initWatchTime() {
await this.$youtube.saveApiStats(
{
cpn: this.cpn,
rt: Math.floor(Date.now() / 1000) - this.startTime,
},
this.playbackTracking.videostatsPlaybackUrl.baseUrl
);
},
},
};
</script>

View File

@ -6,7 +6,6 @@ const url = {
YT_MUSIC_URL: "https://music.youtube.com",
YT_BASE_API: "https://www.youtube.com/youtubei/v1",
YT_SUGGESTIONS: "https://suggestqueries.google.com/complete",
YT_API_STATS: "https://www.youtube.com/api/stats/atr",
VT_GITHUB: "https://api.github.com/repos/Frontesque/VueTube",
};

View File

@ -189,20 +189,13 @@ class Innertube {
}
// WARNING: This is tracking the user's activity, but is required for recommendations to properly work
async apiStats(currentPageType, id, event) {
const params = {
key: this.key,
el: currentPageType,
ns: "yt",
docid: id,
event: event,
feature: "g-high-rec",
c: this.context.client.clientName,
volume: 100,
cver: this.context.client.clientVersion,
hl: this.context.client.hl,
};
await Http.post({ url: constants.URLS.YT_API_STATS, params: params });
async apiStats(params, url) {
console.log(params);
await Http.get({
url: url,
params: params,
headers: this.header,
});
}
// Static methods
@ -303,6 +296,7 @@ class Innertube {
recommendationsContinuation:
columnUI?.continuations[0].reloadContinuationData?.continuation,
},
playbackTracking: responseInfo.playbackTracking,
};
console.log(vidData);

View File

@ -28,8 +28,18 @@ function rgbToHex(r, g, b) {
return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
}
function getCpn() {
const chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
let result = "";
for (let i = 16; i > 0; --i)
result += chars[Math.round(Math.random() * (chars.length - 1))];
return result;
}
module.exports = {
getBetweenStrings,
hexToRgb,
rgbToHex,
getCpn,
};

View File

@ -152,8 +152,8 @@ const innertubeModule = {
}
},
async saveApiStats(currentPageType, id, event) {
await InnertubeAPI.apiStats(currentPageType, id, event);
async saveApiStats(query, url) {
await InnertubeAPI.apiStats(query, url);
},
};