diff --git a/library.json b/library.json index aea716a..8d9f175 100644 --- a/library.json +++ b/library.json @@ -10,7 +10,8 @@ "duration": 1, "partition": "fl2va", "likes": 0, - "createdAt": 1788251288958 + "createdAt": 1788251288958, + "lastPlayedAt": 1788254550116 }, { "id": "8024972d6019", @@ -379,7 +380,7 @@ "likes": 0, "createdAt": 1788181569491, "promptEn": "a massive warship glides through the stars, the bridge suddenly shakes hard", - "lastPlayedAt": 1788253093423 + "lastPlayedAt": 1788254390052 }, { "id": "44bf5b969e94", @@ -424,7 +425,7 @@ "likes": 0, "createdAt": 1788159367556, "promptEn": "constable talking nervously in an old room, looking scared", - "lastPlayedAt": 1788252509170 + "lastPlayedAt": 1788254306023 }, { "id": "a6d767413426", @@ -439,7 +440,7 @@ "likes": 0, "createdAt": 1788157322611, "promptEn": "constable talking, looking scared", - "lastPlayedAt": 1788250916299 + "lastPlayedAt": 1788254071916 }, { "id": "7977eeeabe8b", @@ -469,7 +470,7 @@ "likes": 0, "createdAt": 1788156878022, "promptEn": "constable bursts in and blocks the door, talks to a pale woman behind the screen", - "lastPlayedAt": 1788252225046 + "lastPlayedAt": 1788254209972 }, { "id": "a0b752e7ac0d", @@ -544,7 +545,7 @@ "likes": 0, "createdAt": 1788120171608, "promptEn": "constable bursts in and blocks the door, talks to a pale woman behind the screen", - "lastPlayedAt": 1788252411133 + "lastPlayedAt": 1788254175959 }, { "id": "b8c762a3672e", @@ -574,7 +575,7 @@ "likes": 0, "createdAt": 1788085282201, "promptEn": "constable stands still, quietly says he won't make a sound, woman stays silent behind the screen", - "lastPlayedAt": 1788251288482 + "lastPlayedAt": 1788254023891 }, { "id": "683ab2fd4681", @@ -589,7 +590,7 @@ "likes": 0, "createdAt": 1788071067702, "promptEn": "constable stands still, quietly says he won't make a sound, woman stays silent behind the screen", - "lastPlayedAt": 1788251524674 + "lastPlayedAt": 1788253999874 }, { "id": "2e09fcd3d42c", @@ -649,7 +650,7 @@ "likes": 0, "createdAt": 1788027833836, "promptEn": "wounded hunter at a ruined subway dungeon entrance, lightning flickers, pointing into the tunnel", - "lastPlayedAt": 1788250480118 + "lastPlayedAt": 1788254268003 }, { "id": "cde399c27dc4", @@ -754,7 +755,7 @@ "likes": 0, "createdAt": 1788026568242, "promptEn": "magma commander in a white coat, raising a lava fist on the battlefield", - "lastPlayedAt": 1788252487163 + "lastPlayedAt": 1788254205974 }, { "id": "1442013e94f0", @@ -784,7 +785,7 @@ "likes": 0, "createdAt": 1788025642732, "promptEn": "Vecna reaching out a claw in his lair in the Upside Down, red lightning", - "lastPlayedAt": 1788252559192 + "lastPlayedAt": 1788254043902 }, { "id": "00862f966c3f", @@ -904,7 +905,7 @@ "likes": 0, "createdAt": 1788025220954, "promptEn": "pirate captain plants a cutlass on the table, making a dangerous bargain", - "lastPlayedAt": 1788251456618 + "lastPlayedAt": 1788254095922 }, { "id": "3e03975ff40a", @@ -919,7 +920,7 @@ "likes": 0, "createdAt": 1788025003469, "promptEn": "vampire idol with red eyes, offering a blood-red contract backstage", - "lastPlayedAt": 1788243983188 + "lastPlayedAt": 1788254386050 }, { "id": "2857aacf11bb", @@ -1054,7 +1055,7 @@ "likes": 0, "createdAt": 1788015737873, "promptEn": "pirate negotiation, went in head-on and Barbossa saw it coming", - "lastPlayedAt": 1788252825264 + "lastPlayedAt": 1788254215976 }, { "id": "2a5ee87b8f13", @@ -1174,7 +1175,7 @@ "likes": 0, "createdAt": 1788013870849, "promptEn": "glass bridge game, exploiting a flaw to push back player 101", - "lastPlayedAt": 1788249963847 + "lastPlayedAt": 1788254201971 }, { "id": "dd12bbb55060", @@ -1189,7 +1190,7 @@ "likes": 0, "createdAt": 1788013634333, "promptEn": "glass bridge game, held the goal but paid a price", - "lastPlayedAt": 1788252773238 + "lastPlayedAt": 1788254355883 }, { "id": "5b8ef21616d4", @@ -1294,7 +1295,7 @@ "likes": 0, "createdAt": 1788012441458, "promptEn": "facing Vecna, held the goal but paid a price", - "lastPlayedAt": 1788251798845 + "lastPlayedAt": 1788254283882 }, { "id": "691c2f679759", @@ -1429,7 +1430,7 @@ "likes": 0, "createdAt": 1788010775613, "promptEn": "facing Voldemort, stayed passive and got cornered", - "lastPlayedAt": 1788252633224 + "lastPlayedAt": 1788254061908 }, { "id": "795842efa4a1", @@ -1474,7 +1475,7 @@ "likes": 0, "createdAt": 1788010062535, "promptEn": "facing Thanos, held the goal but paid a price", - "lastPlayedAt": 1788252076972 + "lastPlayedAt": 1788254091920 }, { "id": "324b84ebc607", @@ -1594,7 +1595,7 @@ "likes": 0, "createdAt": 1788008156307, "promptEn": "hotel floor sealed by the Eternity Devil, an endless looping corridor", - "lastPlayedAt": 1788251948919 + "lastPlayedAt": 1788254119932 }, { "id": "cd3b6dda90ad", @@ -1654,7 +1655,7 @@ "likes": 0, "createdAt": 1788007201908, "promptEn": "breaking into the palace, Neferpitou stalling to protect the king", - "lastPlayedAt": 1788252681196 + "lastPlayedAt": 1788254139946 }, { "id": "628026375795", @@ -1699,7 +1700,7 @@ "likes": 0, "createdAt": 1788006252918, "promptEn": "facing L, stayed passive and the evidence pressure maxed out", - "lastPlayedAt": 1788253113430 + "lastPlayedAt": 1788254350039 }, { "id": "0e28a7fecf03", @@ -1939,7 +1940,7 @@ "likes": 0, "createdAt": 1788002929455, "promptEn": "facing Reiner, exploiting a flaw to counter him", - "lastPlayedAt": 1788252195030 + "lastPlayedAt": 1788254288010 }, { "id": "ea8b38014674", @@ -1954,7 +1955,7 @@ "likes": 0, "createdAt": 1788002691822, "promptEn": "facing Reiner, held the goal but paid a price", - "lastPlayedAt": 1788252655237 + "lastPlayedAt": 1788254037898 }, { "id": "f65b15d67966", @@ -2059,7 +2060,7 @@ "likes": 0, "createdAt": 1788001049233, "promptEn": "facing Pain, held the goal but paid a price", - "lastPlayedAt": 1788251780839 + "lastPlayedAt": 1788254115931 }, { "id": "7ecd480a3c31", @@ -2224,7 +2225,7 @@ "likes": 0, "createdAt": 1787900324972, "promptEn": "constable declines a drink, asks what this place is, she watches him through the screen", - "lastPlayedAt": 1788252709206 + "lastPlayedAt": 1788254371882 }, { "id": "43ee5c728f1b", @@ -2434,7 +2435,7 @@ "likes": 0, "createdAt": 1787708713767, "promptEn": "constable stops looking around, turns to face the screen, speaks to her silhouette", - "lastPlayedAt": 1788252811258 + "lastPlayedAt": 1788254322029 }, { "id": "5dc95a15cd5d", @@ -2464,7 +2465,7 @@ "likes": 0, "createdAt": 1787708686397, "promptEn": "constable accepts the invitation, moves to sit on a stool closer to the screen", - "lastPlayedAt": 1788248577063 + "lastPlayedAt": 1788254145947 }, { "id": "ad611bcbd06b", @@ -2539,7 +2540,7 @@ "likes": 0, "createdAt": 1787708648409, "promptEn": "she reaches a hand through the screen gap beckoning him, he steps back and firmly refuses", - "lastPlayedAt": 1788251828861 + "lastPlayedAt": 1788254125936 }, { "id": "3f0c28f5373a", @@ -2749,7 +2750,7 @@ "likes": 0, "createdAt": 1787674688717, "promptEn": "constable agrees to stay, lets his guard down, she gives a barely-there nod", - "lastPlayedAt": 1788250832261 + "lastPlayedAt": 1788254161950 }, { "id": "4874fe3fdbc9", @@ -2779,7 +2780,7 @@ "likes": 0, "createdAt": 1787674642139, "promptEn": "constable forms hand seals and summons a giant toad out of smoke", - "lastPlayedAt": 1788250076972 + "lastPlayedAt": 1788254251995 }, { "id": "e9a6320e18a9", @@ -2854,7 +2855,7 @@ "likes": 0, "createdAt": 1787674446841, "promptEn": "player panics into the room, glances back, out of breath, says something's chasing him outside", - "lastPlayedAt": 1788250856275 + "lastPlayedAt": 1788254185961 }, { "id": "c25d53a5e086", @@ -2869,7 +2870,7 @@ "likes": 0, "createdAt": 1787674395006, "promptEn": "constable and the woman hold a tense silent stare through the screen", - "lastPlayedAt": 1788252897296 + "lastPlayedAt": 1788254099923 }, { "id": "f264bce22903", @@ -2974,7 +2975,7 @@ "likes": 0, "createdAt": 1787640567001, "promptEn": "summoned toad Wentai, beat the painted-skin female ghost", - "lastPlayedAt": 1788252685196 + "lastPlayedAt": 1788254552120 }, { "id": "1cc42a8669a1", @@ -3004,7 +3005,7 @@ "likes": 0, "createdAt": 1787637292741, "promptEn": "constable bursts in panicked, spots the woman behind the screen", - "lastPlayedAt": 1788251872881 + "lastPlayedAt": 1788254566123 }, { "id": "93c2b84a2a10", @@ -3139,7 +3140,7 @@ "likes": 0, "createdAt": 1787588545801, "promptEn": "game menu UI, character swaps out a mechanical arm", - "lastPlayedAt": 1788249159434 + "lastPlayedAt": 1788254406058 }, { "id": "fe749aa533a0", @@ -3184,7 +3185,7 @@ "likes": 0, "createdAt": 1787588544109, "promptEn": "vampire romance drama trailer, girl wanders into the castle", - "lastPlayedAt": 1788250187972 + "lastPlayedAt": 1788254272005 }, { "id": "7ded7db676d3", @@ -3199,7 +3200,7 @@ "likes": 0, "createdAt": 1787588543551, "promptEn": "MiniMax H3 tech brand film, blue waveform logo", - "lastPlayedAt": 1788250954321 + "lastPlayedAt": 1788254047898 }, { "id": "81711242178d", @@ -3244,7 +3245,7 @@ "likes": 0, "createdAt": 1787588541726, "promptEn": "woman opens the trunk on a desert road, takes out a bag", - "lastPlayedAt": 1788251984934 + "lastPlayedAt": 1788254335882 }, { "id": "97846542ab09", @@ -3514,7 +3515,7 @@ "likes": 0, "createdAt": 1787573698485, "promptEn": "underground rap duo freestyling at the camera", - "lastPlayedAt": 1788252777241 + "lastPlayedAt": 1788254075916 }, { "id": "e5246eae814e", @@ -3543,7 +3544,7 @@ "partition": "text", "likes": 0, "createdAt": 1787568512602, - "lastPlayedAt": 1788252082974, + "lastPlayedAt": 1788254065909, "promptEn": "man apologizes, says something outside was chasing him" }, { @@ -3664,7 +3665,7 @@ "likes": 0, "createdAt": 1787470101411, "promptEn": "vertical family-fight drama, arguing with an older woman", - "lastPlayedAt": 1788251276478 + "lastPlayedAt": 1788254011880 }, { "id": "57169e6751d0", @@ -3754,7 +3755,7 @@ "likes": 0, "createdAt": 1787470099306, "promptEn": "late-night laundromat footage mixed with glowing hand-drawn animation", - "lastPlayedAt": 1788251502656 + "lastPlayedAt": 1788254294016 }, { "id": "c0e23beb4132", @@ -3814,7 +3815,7 @@ "likes": 0, "createdAt": 1787455895150, "promptEn": "protagonist leaps across rooftops fleeing pursuers", - "lastPlayedAt": 1788252253059 + "lastPlayedAt": 1788254418063 }, { "id": "2a4e45820135", @@ -4054,7 +4055,7 @@ "likes": 0, "createdAt": 1787413511959, "promptEn": "protagonist leaps across rooftops fleeing pursuers", - "lastPlayedAt": 1788251161159 + "lastPlayedAt": 1788254235982 }, { "id": "5d4f40bf0889", @@ -4099,7 +4100,7 @@ "likes": 0, "createdAt": 1787412150062, "promptEn": "protagonist leaps across rooftops fleeing pursuers", - "lastPlayedAt": 1788252809259 + "lastPlayedAt": 1788254542110 }, { "id": "9d2486d6f969", @@ -4129,7 +4130,7 @@ "likes": 0, "createdAt": 1787388090646, "promptEn": "a cat sitting on a windowsill", - "lastPlayedAt": 1788252393130 + "lastPlayedAt": 1788254205967 }, { "id": "c551457bc55e", @@ -4159,7 +4160,7 @@ "likes": 0, "createdAt": 1787384983124, "promptEn": "protagonist leaps across rooftops fleeing pursuers", - "lastPlayedAt": 1788251892892 + "lastPlayedAt": 1788254354037 }, { "id": "7753a046ba79", @@ -4294,7 +4295,7 @@ "likes": 0, "createdAt": 1787367453839, "promptEn": "golden portal opens, man in green jacket throws a glowing spear", - "lastPlayedAt": 1788252006941 + "lastPlayedAt": 1788254041895 }, { "id": "8402adae59dc", @@ -4309,7 +4310,7 @@ "likes": 0, "createdAt": 1787363956909, "promptEn": "protagonist leaps across rooftops fleeing pursuers", - "lastPlayedAt": 1788250706222 + "lastPlayedAt": 1788254362044 }, { "id": "5692965402f5", @@ -4399,7 +4400,7 @@ "likes": 0, "createdAt": 1787309864037, "promptEn": "transparent gaming mouse product film, blue and orange lighting", - "lastPlayedAt": 1788252567195 + "lastPlayedAt": 1788254219979 }, { "id": "073f5df6427a", @@ -4549,7 +4550,7 @@ "likes": 0, "createdAt": 1787225897650, "promptEn": "constable burns the beauty portrait with the lamp, the door opens on its own", - "lastPlayedAt": 1788253107430 + "lastPlayedAt": 1788254544113 }, { "id": "ed3663dcda7f", diff --git a/server.js b/server.js index 80d0cb9..b5f26e0 100644 --- a/server.js +++ b/server.js @@ -175,6 +175,7 @@ function markPlayed(id) { const clip = lib.find((c) => c.id === id); if (!clip) return; clip.lastPlayedAt = Date.now(); + clip.playCount = (clip.playCount || 0) + 1; saveLibrary(lib); } @@ -225,7 +226,11 @@ function pickNextClip() { // random among everything that isn't on cooldown, no bias toward "fresh". const offCooldown = notPrev.filter((c) => !c.lastPlayedAt || now - c.lastPlayedAt > REPEAT_COOLDOWN_MS); const pool = offCooldown.length ? offCooldown : notPrev; - return { clip: pool[Math.floor(Math.random() * pool.length)], requestedBy: null, chatId: null }; + // Within that pool, only randomize among the least-played clips — keeps + // rotation even instead of letting a few clips dominate airtime by chance. + const minPlays = Math.min(...pool.map((c) => c.playCount || 0)); + const leastPlayed = pool.filter((c) => (c.playCount || 0) === minPlays); + return { clip: leastPlayed[Math.floor(Math.random() * leastPlayed.length)], requestedBy: null, chatId: null }; } const FAILED_ENTRY_TTL_MS = 20000; // how long a "generation failed" entry stays visible before cleanup