2024-05-12 23:34:56 +02:00
<!DOCTYPE html>
< html >
< head >
< meta charset = "UTF-8" / >
< meta name = "viewport" content = "width=device-width, initial-scale=1" / >
< meta property = "og:title" content = "🎵️ TiktOctt" / >
< meta OctoSpaccHubSdk = "Url" content = "https://hub.octt.eu.org/TiktOctt/" / >
< meta OctoSpaccHubSdk = "WebManifestExtra" content = "'display':'standalone', 'icons':[{ 'src':'./logo.png', 'type':'image/png', 'sizes':'1024x1024' }]," / >
< style >
:root {
--footerHeight: 40px;
--hudBaseHeight: 8em;
--screenPadding: 8px;
--hudButtonSize: 48px;
2024-05-15 01:29:32 +02:00
--colorPrimary: #fe77a0;
}
.v-vlite {
--vlite-colorPrimary: var(--colorPrimary) !important;
--vlite-controlBarBackground: none !important;
--vlite-controlBarHeight: 0px !important;
2024-05-12 23:34:56 +02:00
}
body {
margin: 0;
color: white;
background-color: black;
overflow: hidden;
height: 100%;
font-family: sans-serif;
}
a {
color: white !important;
}
.videosContainer {
position: fixed;
height: 100%;
max-height: 100%;
overflow-y: scroll;
scrollbar-width: none;
/* scroll-snap-type: mandatory;
scroll-snap-points-y: repeat(100vh); */
scroll-snap-type: y mandatory;
}
.videoItem {
scroll-snap-align: start;
scroll-snap-stop: always;
position: relative;
/* height: calc(100vh - var(--footerHeight)); */
}
2024-05-15 01:29:32 +02:00
.videosList, .videoItem, .videoItem > *, .videoItem video {
2024-05-12 23:34:56 +02:00
width: 100%;
height: 100%;
2024-05-15 01:29:32 +02:00
aspect-ratio: auto !important;
}
/* .videoItem .vjs-hidden, .videoItem .vjs-text-track-display, .videoItem .vjs-loading-spinner, .videoItem .vjs-big-play-button, .videoItem .vjs-control-bar */
.videoItem .v-controlButton, .videoItem .v-time {
display: none;
}
.videoItem .v-controlBar.v-hidden {
opacity: unset;
2024-05-12 23:34:56 +02:00
}
2024-05-15 01:29:32 +02:00
.videoItem .v-video .v-controlBar {
top: 0;
}
.videoItem > .hud {
2024-05-12 23:34:56 +02:00
position: relative;
bottom: var(--footerHeight);
/* margin: 8px;
z-index: 1;
padding-left: 8px;
padding-right: 8px; */
height: calc(2 * var(--hudBaseHeight) + var(--screenPadding));
bottom: calc(var(--footerHeight) + var(--hudBaseHeight) + var(--screenPadding) + 5em);
background-image: linear-gradient(to bottom, rgba(0,0,0,0.0), rgba(0,0,0,1.0));
}
.videoItem > .hud > * {
position: relative;
}
.videoItem > .hud > p {
/* margin: 8px; */
/* approximation based on all the < p > s */
bottom: calc((-1 * var(--hudBaseHeight)) + (2 * var(--screenPadding)) + 4em - (2 * var(--footerHeight)));
padding-left: var(--screenPadding);
padding-right: var(--screenPadding);
max-width: calc(100vw - (2 * var(--screenPadding)));
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.videoItem > .hud > p.title {
font-size: larger;
margin-top: var(--screenPadding);
padding-bottom: var(--screenPadding);
}
.videoItem > .hud > p.subtitle > a {
text-decoration: none;
}
.videoItem > .hud > p.status {
font-size: small;
top: 11em;
/* margin-bottom: calc(-1 * var(--screenPadding)); */
/* padding-top: var(--screenPadding); */
}
2024-05-15 01:29:32 +02:00
.iconButton, .videoItem > .hud > button {
background: none;
border: none;
}
2024-05-12 23:34:56 +02:00
.videoItem > .hud > button {
position: relative;
display: block;
left: calc(100vw - var(--hudButtonSize) - (2 * var(--screenPadding)));
/* bottom: calc(var(--hudButtonSize) /* + 4em */ /* (2 * var(--screenPadding)) ); */
bottom: calc(4em + (2 * var(--screenPadding)));
margin-bottom: calc(2 * var(--screenPadding));
}
2024-05-15 01:29:32 +02:00
.iconButton, .iconButton > *, .videoItem > .hud > button, .videoItem > .hud > button > * {
2024-05-12 23:34:56 +02:00
width: var(--hudButtonSize);
height: var(--hudButtonSize);
}
.framesArea {
width: 100vw;
height: 100%;
position: absolute;
z-index: 3;
}
.framesArea > * {
margin: 2em;
width: calc(100vw - 4em);
height: calc(100% - 4em);
}
.toastArea {
position: fixed;
z-index: 1;
width: 100vw;
text-align: center;
padding-top: 1em;
top: 1em;
}
.toastArea > span {
padding: 0.5em;
position: fixed;
left: 0;
right: 0;
margin: auto;
width: fit-content;
}
.framesArea > *, .toastArea > span {
background-color: #222;
border-radius: 2px;
}
.contentFrame > .header {
background: black;
padding: 0.5em;
2024-05-15 01:29:32 +02:00
position: relative;
z-index: 1;
2024-05-12 23:34:56 +02:00
}
.contentFrame > .header > button {
float: right;
2024-05-15 01:29:32 +02:00
width: var(--hudButtonSize);
height: var(--hudButtonSize);
font-size: large;
2024-05-12 23:34:56 +02:00
}
.contentFrame > .header > span {
text-overflow: ellipsis;
overflow: hidden;
display: block ruby;
padding-right: var(--screenPadding);
}
.contentFrame > .content {
padding: 1em;
overflow: auto;
2024-05-15 01:29:32 +02:00
width: calc(100% - 2em);
2024-05-12 23:34:56 +02:00
height: calc(100% - 4em);
2024-05-15 01:29:32 +02:00
position: relative;
bottom: calc(var(--hudButtonSize) / 2);
2024-05-12 23:34:56 +02:00
}
.contentFrame > .content * {
max-width: 100% !important;
2024-05-15 01:29:32 +02:00
height: auto !important;
2024-05-12 23:34:56 +02:00
}
2024-05-15 01:29:32 +02:00
.audioNag, button[name="menu"] {
2024-05-12 23:34:56 +02:00
position: fixed;
z-index: 2;
2024-05-15 01:29:32 +02:00
}
.audioNag {
2024-05-12 23:34:56 +02:00
font-size: large;
2024-05-15 01:29:32 +02:00
margin: calc(2 * var(--screenPadding));
}
button[name="menu"] {
right: 0;
margin: var(--screenPadding);
2024-05-12 23:34:56 +02:00
}
.splash {
width: 100%;
height: 100vh;
text-align: center;
background-color: black;
position: absolute;
z-index: 3;
}
.splash > img {
position: absolute;
max-width: 60%;
max-height: 40%;
}
.splash > img, .icon.pause {
margin: auto;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
2024-05-15 01:29:32 +02:00
.splash > p {
position: absolute;
width: 100%;
}
.splash > p[name="nojs"] {
top: 2em;
}
.splash > p[name="loading"] {
bottom: 3em;
}
2024-05-12 23:34:56 +02:00
.footer {
position: fixed;
bottom: 0;
width: 100%;
height: var(--footerHeight);
background: black;
}
< / style >
2024-05-15 01:29:32 +02:00
< link href = "https://cdn.jsdelivr.net/npm/vlitejs@6/dist/vlite.css" rel = "stylesheet" crossorigin / >
< script type = "module" >
import Vlitejs from 'https://cdn.jsdelivr.net/npm/vlitejs@6';
window.Vlitejs = Vlitejs;
< / script >
2024-05-12 23:34:56 +02:00
< / head >
< body >
< div class = "splash" >
2024-05-15 01:29:32 +02:00
< p name = "nojs" > This application requires modern JavaScript. If nothing loads in a while, please check your browser settings.< / p >
2024-05-12 23:34:56 +02:00
< img src = "./logo.png" / >
2024-05-15 01:29:32 +02:00
< p name = "loading" hidden = "true" > ... Loading ...< / p >
2024-05-12 23:34:56 +02:00
< / div >
< div class = "toastArea" > < / div >
< button class = "audioNag" > 🔊️ Unmute< / button >
2024-05-15 01:29:32 +02:00
< button name = "menu" class = "iconButton" >
<!-- Solar Broken Line Icons, Solar Icons: <https://www.svgrepo.com/svg/529002/hamburger - menu> -->
< svg width = "800px" height = "800px" viewBox = "0 0 24 24" fill = "none" xmlns = "http://www.w3.org/2000/svg" > < path d = "M4 7L7 7M20 7L11 7" stroke = "#FFFFFF" stroke-width = "1.5" stroke-linecap = "round" / > < path d = "M20 17H17M4 17L13 17" stroke = "#FFFFFF" stroke-width = "1.5" stroke-linecap = "round" / > < path d = "M4 12H7L20 12" stroke = "#FFFFFF" stroke-width = "1.5" stroke-linecap = "round" / > < / svg >
< / button >
2024-05-12 23:34:56 +02:00
< div class = "framesArea" style = "display: none;" > < / div >
< div class = "videosContainer" >
< div class = "videosList" > < / div >
< / div >
<!-- <div class="footer"></div> -->
< script > ( f u n c t i o n ( ) {
const icons = {
/* Solar Broken Line Icons, Solar Icons */
share /* < https: / / www . svgrepo . com / svg / 529194 / share > */: `< svg width = "800px" height = "800px" viewBox = "0 0 24 24" fill = "none" xmlns = "http://www.w3.org/2000/svg" > < path d = "M4 12C4 13.3807 5.11929 14.5 6.5 14.5C7.88071 14.5 9 13.3807 9 12C9 10.6193 7.88071 9.5 6.5 9.5" stroke = "#FFFFFF" stroke-width = "1.5" stroke-linecap = "round" / > < path d = "M14 6.5L9 10" stroke = "#FFFFFF" stroke-width = "1.5" stroke-linecap = "round" / > < path d = "M14 17.5L9 14" stroke = "#FFFFFF" stroke-width = "1.5" stroke-linecap = "round" / > < path d = "M16.5 21C17.8807 21 19 19.8807 19 18.5C19 17.1193 17.8807 16 16.5 16C15.1193 16 14 17.1193 14 18.5" stroke = "#FFFFFF" stroke-width = "1.5" stroke-linecap = "round" / > < path d = "M18.665 6.74993C17.9746 7.94566 16.4457 8.35535 15.2499 7.66499C14.0542 6.97464 13.6445 5.44566 14.3349 4.24993C15.0252 3.0542 16.5542 2.64451 17.7499 3.33487" stroke = "#FFFFFF" stroke-width = "1.5" stroke-linecap = "round" / > < / svg > `,
2024-05-15 01:29:32 +02:00
heart /* https://www.svgrepo.com/svg/529006/heart-angle */: `< svg width = "800px" height = "800px" viewBox = "0 0 24 24" fill = "none" xmlns = "http://www.w3.org/2000/svg" > < path d = "M12 5.50063L11.4596 6.02073C11.463 6.02421 11.4664 6.02765 11.4698 6.03106L12 5.50063ZM8.96173 18.9109L8.49742 19.4999L8.96173 18.9109ZM15.0383 18.9109L14.574 18.3219L15.0383 18.9109ZM7.00061 16.4209C6.68078 16.1577 6.20813 16.2036 5.94491 16.5234C5.68169 16.8432 5.72758 17.3159 6.04741 17.5791L7.00061 16.4209ZM2.34199 13.4115C2.54074 13.7749 2.99647 13.9084 3.35988 13.7096C3.7233 13.5108 3.85677 13.0551 3.65801 12.6917L2.34199 13.4115ZM13.4698 8.03034C13.7627 8.32318 14.2376 8.32309 14.5304 8.03014C14.8233 7.7372 14.8232 7.26232 14.5302 6.96948L13.4698 8.03034ZM2.75 9.1371C2.75 6.98623 3.96537 5.18252 5.62436 4.42419C7.23607 3.68748 9.40166 3.88258 11.4596 6.02073L12.5404 4.98053C10.0985 2.44352 7.26409 2.02539 5.00076 3.05996C2.78471 4.07292 1.25 6.42503 1.25 9.1371H2.75ZM8.49742 19.4999C9.00965 19.9037 9.55955 20.3343 10.1168 20.6599C10.6739 20.9854 11.3096 21.25 12 21.25V19.75C11.6904 19.75 11.3261 19.6293 10.8736 19.3648C10.4213 19.1005 9.95208 18.7366 9.42605 18.3219L8.49742 19.4999ZM15.5026 19.4999C16.9292 18.3752 18.7528 17.0866 20.1833 15.4758C21.6395 13.8361 22.75 11.8026 22.75 9.1371H21.25C21.25 11.3345 20.3508 13.0282 19.0617 14.4798C17.7469 15.9603 16.0896 17.1271 14.574 18.3219L15.5026 19.4999ZM22.75 9.1371C22.75 6.42503 21.2153 4.07292 18.9992 3.05996C16.7359 2.02539 13.9015 2.44352 11.4596 4.98053L12.5404 6.02073C14.5983 3.88258 16.7639 3.68748 18.3756 4.42419C20.0346 5.18252 21.25 6.98623 21.25 9.1371H22.75ZM14.574 18.3219C14.0479 18.7366 13.5787 19.1005 13.1264 19.3648C12.6739 19.6293 12.3096 19.75 12 19.75V21.25C12.6904 21.25 13.3261 20.9854 13.8832 20.6599C14.4405 20.3343 14.9903 19.9037 15.5026 19.4999L14.574 18.3219ZM9.42605 18.3219C8.63014 17.6945 7.82129 17.0963 7.00061 16.4209L6.04741 17.5791C6.87768 18.2624 7.75472 18.9144 8.49742 19.4999L9.42605 18.3219ZM3.65801 12.6917C3.0968 11.6656 2.75 10.5033 2.75 9.1371H1.25C1.25 10.7746 1.66995 12.1827 2.34199 13.4115L3.65801 12.6917ZM11.4698 6.03106L13.4698 8.03034L14.5302 6.96948L12.5302 4.97021L11.4698 6.03106Z" fill = "#FFFFFF" / > < / svg > `,
2024-05-12 23:34:56 +02:00
heartFilled: `< svg width = "800" height = "800" viewBox = "0 0 24 24" fill = "none" version = "1.1" id = "svg1" xmlns = "http://www.w3.org/2000/svg" xmlns:svg = "http://www.w3.org/2000/svg" > < defs id = "defs1" / > < path style = "fill:#1a1a1a;stroke-width:0.03" d = "m 3 . 066315 , 12 . 73195 c 0 . 028084 , 0 . 16036 0 . 0182 , 0 . 401349 0 . 129264 , 0 . 549433 0 . 1367685 , 0 . 182358 0 . 2882154 , 0 . 404433 0 . 4656573 , 0 . 551831 0 . 1623813 , 0 . 134887 0 . 3395487 , 0 . 262856 0 . 4739676 , 0 . 430879 0 . 1026399 , 0 . 1283 0 . 198972 , 0 . 259487 0 . 3016158 , 0 . 387792 0 . 113082 , 0 . 141353 0 . 2792253 , 0 . 243037 0 . 3877917 , 0 . 387792 0 . 1058463 , 0 . 01536 0 . 017894 , 0 . 09147 0 . 043088 , 0 . 129264 0 . 086176 , 0 . 129264 0 . 064632 , 0 . 04309 0 . 129264 , 0 . 129264 0 . 050249 , 0 . 067 0 . 079015 , 0 . 148441 0 . 1292637 , 0 . 21544 0 . 1206465 , 0 . 160862 0 . 3102336 , 0 . 270018 0 . 4308798 , 0 . 43088 0 . 038539 , 0 . 05138 0 . 050547 , 0 . 118907 0 . 086176 , 0 . 172351 0 . 1833339 , 0 . 275001 0 . 5073621 , 0 . 476344 0 . 7245111 , 0 . 724515 0 . 1367106 , 0 . 156241 0 . 240207 , 0 . 401749 0 . 4267035 , 0 . 512876 0 . 3683847 , 0 . 219506 -0 . 077144 , -0 . 108835 0 . 4309008 , 0 . 259937 0 . 135204 , 0 . 09814 0 . 2675682 , 0 . 200385 0 . 395913 , 0 . 307339 0 . 046812 , 0 . 03901 0 . 07523 , 0 . 101095 0 . 1292637 , 0 . 129264 0 . 1558587 , 0 . 08125 0 . 3492555 , 0 . 173342 0 . 5007084 , 0 . 254199 0 . 094294 , 0 . 05034 0 . 2782896 , 0 . 248841 0 . 3396438 , 0 . 296561 0 . 1133721 , 0 . 08818 0 . 2343669 , 0 . 16658 0 . 3447039 , 0 . 258528 0 . 062416 , 0 . 05201 0 . 1073538 , 0 . 123603 0 . 1723518 , 0 . 172352 0 . 025693 , 0 . 01927 0 . 063395 , 0 . 02045 0 . 086176 , 0 . 04309 0 . 042571 , 0 . 0423 0 . 013019 , 0 . 09028 0 . 1160094 , 0 . 158551 0 . 1583157 , 0 . 104939 0 . 3542865 , 0 . 179986 0 . 5170557 , 0 . 258528 0 . 034114 , 0 . 01646 0 . 04779 , 0 . 061 0 . 080348 , 0 . 08035 0 . 099533 , 0 . 05917 0 . 194102 , 0 . 129347 0 . 301616 , 0 . 172352 0 . 02667 , 0 . 01067 0 . 0601 , -0 . 01206 0 . 08618 , 0 0 . 01206 , 0 . 0056 -0 . 0094 , 0 . 03046 0 , 0 . 03986 0 . 03557 , 0 . 03557 0 . 74521 , 0 . 359462 0 . 85853 , 0 . 387791 0 . 12282 , 0 . 03071 0 . 260911 , 0 . 01121 0 . 383184 , 0 . 04309 0 . 173419 , 0 . 04521 0 . 346389 , 0 . 08748 0 . 513521 , 0 . 129264 0 . 0784 , 0 . 0196 0 . 746728 , 0 . 0091 0 . 758994 , 0 0 . 03247 , -0 . 02399 0 . 05101 , -0 . 06489 0 . 08564 , -0 . 08564 0 . 337832 , -0 . 202359 0 . 0222 , 0 . 0058 0 . 172352 , -0 . 04309 0 . 19465 , -0 . 06343 0 . 424696 , -0 . 140439 0 . 597656 , -0 . 255746 0 . 40672 , -0 . 271147 0 . 842162 , -0 . 507739 1 . 279142 , -0 . 726244 0 . 279123 , -0 . 139571 0 . 672905 , -0 . 629816 0 . 899325 , -0 . 856237 0 . 112345 , -0 . 112345 0 . 220742 , -0 . 245534 0 . 344704 , -0 . 344704 0 . 08087 , -0 . 0647 0 . 177652 , -0 . 107651 0 . 258528 , -0 . 172351 0 . 06344 , -0 . 05076 0 . 105083 , -0 . 126788 0 . 172351 , -0 . 172352 0 . 01133 , -0 . 0077 0 . 03137 , 0 . 0097 0 . 04105 , 0 0 . 07641 , -0 . 07642 0 . 120947 , -0 . 185623 0 . 21544 , -0 . 256493 0 . 04162 , -0 . 03122 0 . 27234 , -0 . 175455 0 . 293894 , -0 . 208262 0 . 07414 , -0 . 112851 0 . 148749 , -0 . 211939 0 . 258528 , -0 . 299762 0 . 190576 , -0 . 152461 0 . 409093 , -0 . 275569 0 . 603231 , -0 . 43088 0 . 03172 , -0 . 02538 0 . 04984 , -0 . 06801 0 . 08618 , -0 . 08618 0 . 02569 , -0 . 01285 0 . 06048 , 0 . 01285 0 . 08618 , 0 0 . 01285 , -0 . 0064 -0 . 008 , -0 . 03114 0 , -0 . 04309 0 . 02253 , -0 . 0338 0 . 05368 , -0 . 0618 0 . 08618 , -0 . 08618 0 . 07698 , -0 . 05773 0 . 19126 , -0 . 105363 0 . 258528 , -0 . 172352 0 . 02264 , -0 . 02255 0 . 02049 , -0 . 06304 0 . 04309 , -0 . 08564 0 . 05139 , -0 . 05139 0 . 159407 , -0 . 101516 0 . 214903 , -0 . 129264 0 . 08322 , -0 . 04161 0 . 140935 , -0 . 159561 0 . 21544 , -0 . 21544 0 . 318335 , -0 . 238751 0 . 661753 , -0 . 494545 0 . 904847 , -0 . 818671 0 . 210029 , -0 . 280038 0 . 297878 , -0 . 594508 0 . 43088 , -0 . 904847 0 . 03795 , -0 . 08856 0 . 0988 , -0 . 167125 0 . 129264 , -0 . 258528 0 . 01408 , -0 . 04225 0 . 05505 , -0 . 249747 0 . 08618 , -0 . 301616 0 . 0209 , -0 . 03483 0 . 06527 , -0 . 05134 0 . 08618 , -0 . 08618 0 . 05429 , -0 . 09048 0 . 319738 , -0 . 636681 0 . 342674 , -0 . 728424 0 . 007 , -0 . 02787 -0 . 0091 , -0 . 05893 0 , -0 . 08618 0 . 02212 , -0 . 06637 0 . 106845 , -0 . 142684 0 . 129264 , -0 . 20994 0 . 0091 , -0 . 02725 -0 . 007 , -0 . 05831 0 , -0 . 08618 0 . 02007 , -0 . 08029 0 . 106379 , -0 . 146786 0 . 129263 , -0 . 21544 0 . 0208 , -0 . 06239 0 . 01343 , -0 . 153282 0 . 04309 , -0 . 211664 0 . 04864 , -0 . 09575 0 . 144642 , -0 . 192383 0 . 171816 , -0 . 301078 0 . 03314 , -0 . 132559 -0 . 005 , -0 . 269773 0 . 03874 , -0 . 401057 0 . 02031 , -0 . 06094 0 . 06362 , -0 . 11221 0 . 08618 , -0 . 172352 0 . 0619 , -0 . 165077 0 . 07349 , -0 . 349735 0 . 129264 , -0 . 517056 0 . 162666 , -0 . 4879978 0 . 290899 , -1 . 0797424 0 . 21544 , -1 . 6079563 -0 . 01946 , -0 . 1362099 -0 . 05541 , -0 . 2647371 -0 . 08618 , -0 . 3877917 -0 . 01558 , -0 . 062314 -0 . 0706 , -0 . 1100379 -0 . 08618 , -0 . 1723521 -0 . 02015 , -0 . 080614 -0 . 01703 , -0 . 18036 -0 . 04309 , -0 . 2585277 -0 . 0064 , -0 . 01927 -0 . 034 , -0 . 02492 -0 . 04309 , -0 . 043088 -0 . 04957 , -0 . 099146 -0 . 176085 , -0 . 4359618 -0 . 214893 , -0 . 5590665 -0 . 07577 , -0 . 2403648 -0 . 11693
};
let currentVideoElem, firstVideoElem, lastVideoElem;
2024-05-15 01:29:32 +02:00
let isSplashNeeded = true;
2024-05-12 23:34:56 +02:00
let userInteracted = false;
2024-05-15 01:29:32 +02:00
let preloadCount = 5;
const backendFetchCount = 50;
const videoObjects = [];
2024-05-12 23:34:56 +02:00
const postsCache = {};
const postsHistory = [];
const scrollObserver = new IntersectionObserver(function(elems){
for (elem of elems) {
if (elem.isIntersecting) {
addVideos();
}
}
});
function jsonStorageApi (key, setValue) {
return JSON.parse(localStorage[setValue !== undefined ? 'setItem' : 'getItem'](
`org.eu.octt.TiktOctt/v1/${key}`, JSON.stringify(setValue)) || '{}');
}
async function pullNewVideos () {
const siteUrl = 'https://octospacc.altervista.org';
2024-05-15 01:29:32 +02:00
const postsRequest = await fetch(`${siteUrl}/wp-content/uploads/${siteUrl.split('.').slice(0, 1)[0].split('//')[1]}/scripts/stuff.php?&Thing=SiteWpJsonCors&AccessToken=9ab6e20c&$Query=${encodeURIComponent(`wp/v2/posts?orderby=rand&search=wp-block-video&per_page=${backendFetchCount}`)}`);
2024-05-12 23:34:56 +02:00
const postsData = await postsRequest.json();
for (const post of postsData) {
if (!postsCache[post.id]) {
post.videoUrl = post.content.rendered.split('< / video > ')[0].split('>').slice(-2)[0].split('src="')[1].split('"')[0]
.replaceAll(`${siteUrl}/wp-content/uploads/`, 'https://octospacc.github.io/microblog-mirror/assets/uploads/');
post.contentHtml = post.content.rendered
.replaceAll('< video ' , ' < ! -- < video ' ) . replaceAll ( ' < / video > ', '< / video-none > -->');
try {
const videoRequest = await fetch(post.videoUrl, { method: 'HEAD' });
if (videoRequest.status === 200) {
postsCache[post.id] = post;
}
} catch(err) {}
}
}
}
function onUserInteract () {
userInteracted = true;
if (currentVideoElem) {
2024-05-15 01:29:32 +02:00
//currentVideoElem.muted = false;
videoObjects[currentVideoElem.dataset.videoId].player.unMute();
2024-05-12 23:34:56 +02:00
}
document.querySelector('.audioNag')?.remove();
}
2024-05-15 01:29:32 +02:00
// TODO fix something here or in posts fetching,
// when not enough posts are fetched from the backend we might end up never fecthing new ones here
2024-05-12 23:34:56 +02:00
function theAlgorithm () {
const posts = Object.values(postsCache);
let post = posts[~~(Math.random() * posts.length)];
// ensure a new random video is shown until we exhaust them
if (postsHistory.includes(post.id) & & (postsHistory.length < posts.length ) ) {
while (postsHistory.includes(post.id)) {
post = posts[~~(Math.random() * posts.length)];
}
}
// TODO fix never show the exact same post two times next to each other, leads to frustration
//while (post.id === postsHistory.slice(-1)[0]) {
// post = posts[~~(Math.random() * posts.length)];
//}
postsHistory.push(post.id);
return post;
}
function cleanExcerpt (text) {
text = text.trim();
if (text.split('>')[0].split(' ')[0].split('< ')[1] === 'div') {
text = text.split('< / div > ')[1];
}
return text.replaceAll('< p > ', '').replaceAll('< p ' , ' ' ) . replaceAll ( ' < / p > ', '');
}
function heartPostState (id, buttonElem, invert) {
let hearts = jsonStorageApi('hearts');
if (invert) {
hearts[id] = !hearts[id];
jsonStorageApi('hearts', hearts);
}
const icon = icons[hearts[id] ? 'heartFilled' : 'heart'];
if (buttonElem) {
buttonElem.innerHTML = icon;
}
return icon;
}
2024-05-15 01:29:32 +02:00
function shareLink (event) {
event.stopPropagation();
2024-05-12 23:34:56 +02:00
var link = this.parentElement.querySelector('a').href;
navigator.clipboard.writeText(link);
document.querySelector('.toastArea').appendChild(Object.assign(document.createElement('span'), { innerHTML: `Copied to clipboard` }));
setTimeout((function(){
document.querySelector('.toastArea > span').remove();
}), 2000);
if ('share' in navigator) {
2024-05-15 01:29:32 +02:00
navigator.share({ url: link });
2024-05-12 23:34:56 +02:00
}
}
2024-05-15 01:29:32 +02:00
function openContentFrame (content, name, link) {
const framesAreaElem = document.querySelector('.framesArea');
framesAreaElem.style.display = null;
framesAreaElem.innerHTML = `< div class = "contentFrame" >
< div class = "header" >
< button > ❌️< / button >
< span > ${link ? `< a target = "_blank" href = "${link}" > ${name}< / a > ` : name}< / span >
< / div >
< div class = "content" > ${content}< / div >
< / div > `;
document.querySelector('.framesArea > .contentFrame > .header > button').onclick = (function(){
this.parentElement.parentElement.remove();
framesAreaElem.style.display = 'none';
});
}
2024-05-12 23:34:56 +02:00
async function addVideos () {
const fragmentElem = document.createDocumentFragment();
if (firstVideoElem) { //if (lastVideoElem) {
scrollObserver.unobserve(firstVideoElem);//lastVideoElem);
firstVideoElem = null;
}
try {
await pullNewVideos();
} catch(err) {
setTimeout(addVideos, 1000);
return;
}
for (let i=0; i< preloadCount ; i + + ) {
const post = theAlgorithm();
const itemElem = document.createElement('div');
const hudElem = Object.assign(document.createElement('div'), {
className: 'hud',
2024-05-15 01:29:32 +02:00
onclick: (function(){ this.parentElement.querySelector('.v-video .v-overlay').click() }),
2024-05-12 23:34:56 +02:00
innerHTML /* using nbsp to preserve vertical alignment */: `
< p class = "status" > < a target = "_blank" href = "${post.link}" > ${post.date}< / a > < / p >
< p class = "title" > < a href = "javascript:void(0);" > ${post.title.rendered || '...'}< / a > < / p >
< p class = "subtitle" > < a href = "javascript:void(0);" > ${cleanExcerpt(post.excerpt.rendered) || ' '}< / a > < / p >
< button name = "heart" > ${heartPostState(post.guid.rendered)}< / button >
< button name = "share" > ${icons.share}< / button >
`,
});
2024-05-15 01:29:32 +02:00
hudElem.querySelector('p.status').onclick = (function(event){ event.stopPropagation(); });
hudElem.querySelector('p.title').onclick = hudElem.querySelector('p.subtitle').onclick = (function(event){
event.stopPropagation();
openContentFrame(post.content.rendered, post.link, post.link);
});
hudElem.querySelector('button[name="heart"]').onclick = (function(event){
event.stopPropagation();
heartPostState(post.guid.rendered, this, true)
2024-05-12 23:34:56 +02:00
});
hudElem.querySelector('button[name="share"]').onclick = shareLink;
const videoElem = Object.assign(document.createElement('video'), {
src: post.videoUrl,
muted: true, loop: true, //autoplay: true,
2024-05-15 01:29:32 +02:00
//onclick: onClickVideo,
2024-05-12 23:34:56 +02:00
onerror: (function(){
// we must find a clean way to make the video autoplay if it loads
// after failing the first time, this is just a temporary patch
//this.autoplay = true;
2024-05-15 01:29:32 +02:00
// TODO make this only try to reload the video when it's actually the current
// because onerror triggers only when an error first happens and not constantly
//videoObjects[currentVideoElem.dataset.videoId].loading(true);
2024-05-12 23:34:56 +02:00
this.load();
2024-05-15 01:29:32 +02:00
//if (currentVideoElem === this) {
// videoObject.play();
// //this.play();
//}
2024-05-12 23:34:56 +02:00
}),
2024-05-15 01:29:32 +02:00
//oncanplay: onCanPlayVideo,
2024-05-12 23:34:56 +02:00
});
2024-05-15 01:29:32 +02:00
//videoElem.load();
2024-05-12 23:34:56 +02:00
//videoElem.play();
//lastVideoElem = videoElem;
if (!firstVideoElem) {
firstVideoElem = videoElem;
2024-05-15 01:29:32 +02:00
if (!currentVideoElem) {
currentVideoElem = videoElem;
}
2024-05-12 23:34:56 +02:00
}
itemElem.classList.add('videoItem');
itemElem.appendChild(videoElem);
2024-05-15 01:29:32 +02:00
//const videoObject = videojs(videoElem);
//videoElem.dataset.videoObject = (videoObjects.push(videoObject) - 1);
2024-05-12 23:34:56 +02:00
itemElem.appendChild(hudElem);
fragmentElem.appendChild(itemElem);
2024-05-15 01:29:32 +02:00
const videoObject = await new Vlitejs(videoElem, { onReady: onCanPlayVideo });
videoElem.dataset.videoId = (videoObjects.push(videoObject) - 1);
videoElem.parentElement.onclick = onClickVideo;
2024-05-12 23:34:56 +02:00
}
document.querySelector('.videosList').appendChild(fragmentElem);
scrollObserver.observe(firstVideoElem);//lastVideoElem);
}
addVideos();
//preloadCount -= 1;
2024-05-15 01:29:32 +02:00
function onClickVideo () {
//currentVideoElem = (this.querySelector('video') || this);
//if (userInteracted) {
//currentVideoElem[currentVideoElem.paused ? 'play' : 'pause']();
//} else {
if (!userInteracted) {
onUserInteract();
videoObjects[this.querySelector('video').dataset.videoId].player.play();
}
}
function onCanPlayVideo () {
const videoElem = (this.media || this);
const splashElem = document.querySelector('.splash');
if (splashElem & & (document.querySelector('.videoItem video' /* first item */) === videoElem)) {
isSplashNeeded = false;
setTimeout((function(){
videoElem.currentTime = 0;
document.querySelector('.splash')?.remove();
const videoObject = videoObjects[videoElem.dataset.videoId];
videoObject.player.play();
if (userInteracted) {
videoObject.player.unMute();
}
videoElem.parentElement.focus();
}), 1000);
}
}
2024-05-12 23:34:56 +02:00
// TODO fix, it's kinda bad with touch scrolling since so many events are generated;
// pausing should maybe only happen when the scoll is completed?
function onVideosScroll () {
//let alignedVideoElem;
2024-05-15 01:29:32 +02:00
for (const videoElem of document.querySelectorAll('.videoItem video')) {
const videoObject = videoObjects[videoElem.dataset.videoId];
2024-05-12 23:34:56 +02:00
if (~~videoElem.getBoundingClientRect().y === 0) {
/* alignedVideoElem = */ currentVideoElem = videoElem;
2024-05-15 01:29:32 +02:00
//videoElem.load();
//videoElem.currentTime = 0;
//videoElem.play();
videoObject.player.seekTo(0);
videoObject.player.play();
2024-05-12 23:34:56 +02:00
if (userInteracted) {
2024-05-15 01:29:32 +02:00
//videoElem.muted = false;
videoObject.player.unMute();
2024-05-12 23:34:56 +02:00
}
2024-05-15 01:29:32 +02:00
videoElem.parentElement.focus();
2024-05-12 23:34:56 +02:00
} else {
2024-05-15 01:29:32 +02:00
//videoElem.pause();
//videoElem.currentTime = 0;
videoObject.player.pause();
videoObject.player.seekTo(0);
2024-05-12 23:34:56 +02:00
}
}
2024-05-15 01:29:32 +02:00
//document.querySelector('.videoItem').remove();
//videoObjects.pop();
2024-05-12 23:34:56 +02:00
//if (alignedVideoElem) {}
}
document.querySelector('.videosContainer').onscroll = onVideosScroll;
document.querySelector('.videosContainer').focus();
onVideosScroll();
document.querySelector('.splash').onclick = onUserInteract;
document.querySelector('.audioNag').onclick = (function(){
onUserInteract();
document.querySelector('.videosContainer').focus();
});
2024-05-15 01:29:32 +02:00
document.querySelector('button[name="menu"]').onclick = (function(){
openContentFrame(`< p >
TiktOctt is a personal experiment to display video posts from my WordPress MicroBlog (< a target = "_blank" href = "https://octospacc.altervista.org/" > octospacc.altervista.org< / a > ) in a short-form, infinite-scrolling feed fashion. It uses the WordPress JSON REST API to fetch data, and a couple of hacks to cope with infrastructural problems of the video streaming. Once I manage to fix the big issues and get around to tweaking the suggestion algorithm better, this will be made officially available for other sites and possibly platforms. — < a href = "https://octospacc.altervista.org/2024/05/13/dickcock-more-like-tiktoctt/" > See the presentation video< / a > . Also visit < a href = "../" > the OctoSpacc Hub< / a > for more.
< / p > < h3 > Changelog< / h3 >
< h4 > 2024-05-14< / h4 > < ul >
< li > Add the menu button and this about dialogue.< / li >
< li > Migrate video player from HTML5 to < a target = "blank" href = "https://github.com/vlitejs/vlite" > vLitejs< / a > , and tweak some video loading code, to hopefully fix all streaming issues on mobile browsers.< / li >
< li > Thanks to the new video player, we also now have a pause indicator button and seekbar for free, and pause video on desktop via spacebar.< / li >
< li > Slightly improve the UI, allowing for a bigger click area for play/pause, better popup dialogue with well-scaled content and bigger button, and better keyboard navigation with TABbing on buttons.< / li >
< li > Fix the share feature, which only copied but failed to open the mobile share menu.< / li >
< li > Add no-JS warning on the splash screen, and also a fallback "Loading..." text if bootup takes longer than expected (to not make the app seem frozen).< / li >
< / ul >
< h4 > 2024-05-12< / h4 > < ul >
< li > First MVP version.< / li >
< li > Basic UI, with post date, link, title, description, and preview, share and like features.< / li >
< li > Basic random suggestion algorithm, providing unique posts until they are exhausted, then going in true random.< / li >
< / ul > < p >
Copyright notice: all code and assets that comprise this program are either original, or used with permission from the copyright holder(s) by the terms of free and open-source licenses. This application is an original and independent project, and is not in any way correlated with, or endorsed by, any other software that appears similar in branding, coding, appearance, or functionality.
< / p > `, 'About TiktOctt (v2024-05-14)');
});
2024-05-12 23:34:56 +02:00
2024-05-15 01:29:32 +02:00
document.querySelector('.splash > p[name="nojs"]').remove();
2024-05-12 23:34:56 +02:00
currentVideoElem = document.querySelector('video');
2024-05-15 01:29:32 +02:00
setInterval((function(){
const splashElem = document.querySelector('.splash > p[name="loading"]');
if (isSplashNeeded & & splashElem) {
splashElem.hidden = false;
}
}), 3000);
2024-05-12 23:34:56 +02:00
})();< / script >
< / body >
< / html >