Commit edfe6543d392231836d45c98040351895ae4868b
1 parent
fcb9a940
Rotate the random fallback pick by play count, not pure chance
Adds playCount to each library clip, incremented every time it airs (markPlayed). The idle-filler pick in pickNextClip now randomizes only among the least-played clips in the eligible pool — everyone at 0 plays first, then everyone tied at 1, and so on — instead of picking uniformly at random across the whole library, which let some clips rack up far more airtime than others by chance alone. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0144MiJ2cLjMd9jWaeEpVfUJ
Showing
2 changed files
with
59 additions
and
53 deletions
library.json
| @@ -10,7 +10,8 @@ | @@ -10,7 +10,8 @@ | ||
| 10 | "duration": 1, | 10 | "duration": 1, |
| 11 | "partition": "fl2va", | 11 | "partition": "fl2va", |
| 12 | "likes": 0, | 12 | "likes": 0, |
| 13 | - "createdAt": 1788251288958 | 13 | + "createdAt": 1788251288958, |
| 14 | + "lastPlayedAt": 1788254550116 | ||
| 14 | }, | 15 | }, |
| 15 | { | 16 | { |
| 16 | "id": "8024972d6019", | 17 | "id": "8024972d6019", |
| @@ -379,7 +380,7 @@ | @@ -379,7 +380,7 @@ | ||
| 379 | "likes": 0, | 380 | "likes": 0, |
| 380 | "createdAt": 1788181569491, | 381 | "createdAt": 1788181569491, |
| 381 | "promptEn": "a massive warship glides through the stars, the bridge suddenly shakes hard", | 382 | "promptEn": "a massive warship glides through the stars, the bridge suddenly shakes hard", |
| 382 | - "lastPlayedAt": 1788253093423 | 383 | + "lastPlayedAt": 1788254390052 |
| 383 | }, | 384 | }, |
| 384 | { | 385 | { |
| 385 | "id": "44bf5b969e94", | 386 | "id": "44bf5b969e94", |
| @@ -424,7 +425,7 @@ | @@ -424,7 +425,7 @@ | ||
| 424 | "likes": 0, | 425 | "likes": 0, |
| 425 | "createdAt": 1788159367556, | 426 | "createdAt": 1788159367556, |
| 426 | "promptEn": "constable talking nervously in an old room, looking scared", | 427 | "promptEn": "constable talking nervously in an old room, looking scared", |
| 427 | - "lastPlayedAt": 1788252509170 | 428 | + "lastPlayedAt": 1788254306023 |
| 428 | }, | 429 | }, |
| 429 | { | 430 | { |
| 430 | "id": "a6d767413426", | 431 | "id": "a6d767413426", |
| @@ -439,7 +440,7 @@ | @@ -439,7 +440,7 @@ | ||
| 439 | "likes": 0, | 440 | "likes": 0, |
| 440 | "createdAt": 1788157322611, | 441 | "createdAt": 1788157322611, |
| 441 | "promptEn": "constable talking, looking scared", | 442 | "promptEn": "constable talking, looking scared", |
| 442 | - "lastPlayedAt": 1788250916299 | 443 | + "lastPlayedAt": 1788254071916 |
| 443 | }, | 444 | }, |
| 444 | { | 445 | { |
| 445 | "id": "7977eeeabe8b", | 446 | "id": "7977eeeabe8b", |
| @@ -469,7 +470,7 @@ | @@ -469,7 +470,7 @@ | ||
| 469 | "likes": 0, | 470 | "likes": 0, |
| 470 | "createdAt": 1788156878022, | 471 | "createdAt": 1788156878022, |
| 471 | "promptEn": "constable bursts in and blocks the door, talks to a pale woman behind the screen", | 472 | "promptEn": "constable bursts in and blocks the door, talks to a pale woman behind the screen", |
| 472 | - "lastPlayedAt": 1788252225046 | 473 | + "lastPlayedAt": 1788254209972 |
| 473 | }, | 474 | }, |
| 474 | { | 475 | { |
| 475 | "id": "a0b752e7ac0d", | 476 | "id": "a0b752e7ac0d", |
| @@ -544,7 +545,7 @@ | @@ -544,7 +545,7 @@ | ||
| 544 | "likes": 0, | 545 | "likes": 0, |
| 545 | "createdAt": 1788120171608, | 546 | "createdAt": 1788120171608, |
| 546 | "promptEn": "constable bursts in and blocks the door, talks to a pale woman behind the screen", | 547 | "promptEn": "constable bursts in and blocks the door, talks to a pale woman behind the screen", |
| 547 | - "lastPlayedAt": 1788252411133 | 548 | + "lastPlayedAt": 1788254175959 |
| 548 | }, | 549 | }, |
| 549 | { | 550 | { |
| 550 | "id": "b8c762a3672e", | 551 | "id": "b8c762a3672e", |
| @@ -574,7 +575,7 @@ | @@ -574,7 +575,7 @@ | ||
| 574 | "likes": 0, | 575 | "likes": 0, |
| 575 | "createdAt": 1788085282201, | 576 | "createdAt": 1788085282201, |
| 576 | "promptEn": "constable stands still, quietly says he won't make a sound, woman stays silent behind the screen", | 577 | "promptEn": "constable stands still, quietly says he won't make a sound, woman stays silent behind the screen", |
| 577 | - "lastPlayedAt": 1788251288482 | 578 | + "lastPlayedAt": 1788254023891 |
| 578 | }, | 579 | }, |
| 579 | { | 580 | { |
| 580 | "id": "683ab2fd4681", | 581 | "id": "683ab2fd4681", |
| @@ -589,7 +590,7 @@ | @@ -589,7 +590,7 @@ | ||
| 589 | "likes": 0, | 590 | "likes": 0, |
| 590 | "createdAt": 1788071067702, | 591 | "createdAt": 1788071067702, |
| 591 | "promptEn": "constable stands still, quietly says he won't make a sound, woman stays silent behind the screen", | 592 | "promptEn": "constable stands still, quietly says he won't make a sound, woman stays silent behind the screen", |
| 592 | - "lastPlayedAt": 1788251524674 | 593 | + "lastPlayedAt": 1788253999874 |
| 593 | }, | 594 | }, |
| 594 | { | 595 | { |
| 595 | "id": "2e09fcd3d42c", | 596 | "id": "2e09fcd3d42c", |
| @@ -649,7 +650,7 @@ | @@ -649,7 +650,7 @@ | ||
| 649 | "likes": 0, | 650 | "likes": 0, |
| 650 | "createdAt": 1788027833836, | 651 | "createdAt": 1788027833836, |
| 651 | "promptEn": "wounded hunter at a ruined subway dungeon entrance, lightning flickers, pointing into the tunnel", | 652 | "promptEn": "wounded hunter at a ruined subway dungeon entrance, lightning flickers, pointing into the tunnel", |
| 652 | - "lastPlayedAt": 1788250480118 | 653 | + "lastPlayedAt": 1788254268003 |
| 653 | }, | 654 | }, |
| 654 | { | 655 | { |
| 655 | "id": "cde399c27dc4", | 656 | "id": "cde399c27dc4", |
| @@ -754,7 +755,7 @@ | @@ -754,7 +755,7 @@ | ||
| 754 | "likes": 0, | 755 | "likes": 0, |
| 755 | "createdAt": 1788026568242, | 756 | "createdAt": 1788026568242, |
| 756 | "promptEn": "magma commander in a white coat, raising a lava fist on the battlefield", | 757 | "promptEn": "magma commander in a white coat, raising a lava fist on the battlefield", |
| 757 | - "lastPlayedAt": 1788252487163 | 758 | + "lastPlayedAt": 1788254205974 |
| 758 | }, | 759 | }, |
| 759 | { | 760 | { |
| 760 | "id": "1442013e94f0", | 761 | "id": "1442013e94f0", |
| @@ -784,7 +785,7 @@ | @@ -784,7 +785,7 @@ | ||
| 784 | "likes": 0, | 785 | "likes": 0, |
| 785 | "createdAt": 1788025642732, | 786 | "createdAt": 1788025642732, |
| 786 | "promptEn": "Vecna reaching out a claw in his lair in the Upside Down, red lightning", | 787 | "promptEn": "Vecna reaching out a claw in his lair in the Upside Down, red lightning", |
| 787 | - "lastPlayedAt": 1788252559192 | 788 | + "lastPlayedAt": 1788254043902 |
| 788 | }, | 789 | }, |
| 789 | { | 790 | { |
| 790 | "id": "00862f966c3f", | 791 | "id": "00862f966c3f", |
| @@ -904,7 +905,7 @@ | @@ -904,7 +905,7 @@ | ||
| 904 | "likes": 0, | 905 | "likes": 0, |
| 905 | "createdAt": 1788025220954, | 906 | "createdAt": 1788025220954, |
| 906 | "promptEn": "pirate captain plants a cutlass on the table, making a dangerous bargain", | 907 | "promptEn": "pirate captain plants a cutlass on the table, making a dangerous bargain", |
| 907 | - "lastPlayedAt": 1788251456618 | 908 | + "lastPlayedAt": 1788254095922 |
| 908 | }, | 909 | }, |
| 909 | { | 910 | { |
| 910 | "id": "3e03975ff40a", | 911 | "id": "3e03975ff40a", |
| @@ -919,7 +920,7 @@ | @@ -919,7 +920,7 @@ | ||
| 919 | "likes": 0, | 920 | "likes": 0, |
| 920 | "createdAt": 1788025003469, | 921 | "createdAt": 1788025003469, |
| 921 | "promptEn": "vampire idol with red eyes, offering a blood-red contract backstage", | 922 | "promptEn": "vampire idol with red eyes, offering a blood-red contract backstage", |
| 922 | - "lastPlayedAt": 1788243983188 | 923 | + "lastPlayedAt": 1788254386050 |
| 923 | }, | 924 | }, |
| 924 | { | 925 | { |
| 925 | "id": "2857aacf11bb", | 926 | "id": "2857aacf11bb", |
| @@ -1054,7 +1055,7 @@ | @@ -1054,7 +1055,7 @@ | ||
| 1054 | "likes": 0, | 1055 | "likes": 0, |
| 1055 | "createdAt": 1788015737873, | 1056 | "createdAt": 1788015737873, |
| 1056 | "promptEn": "pirate negotiation, went in head-on and Barbossa saw it coming", | 1057 | "promptEn": "pirate negotiation, went in head-on and Barbossa saw it coming", |
| 1057 | - "lastPlayedAt": 1788252825264 | 1058 | + "lastPlayedAt": 1788254215976 |
| 1058 | }, | 1059 | }, |
| 1059 | { | 1060 | { |
| 1060 | "id": "2a5ee87b8f13", | 1061 | "id": "2a5ee87b8f13", |
| @@ -1174,7 +1175,7 @@ | @@ -1174,7 +1175,7 @@ | ||
| 1174 | "likes": 0, | 1175 | "likes": 0, |
| 1175 | "createdAt": 1788013870849, | 1176 | "createdAt": 1788013870849, |
| 1176 | "promptEn": "glass bridge game, exploiting a flaw to push back player 101", | 1177 | "promptEn": "glass bridge game, exploiting a flaw to push back player 101", |
| 1177 | - "lastPlayedAt": 1788249963847 | 1178 | + "lastPlayedAt": 1788254201971 |
| 1178 | }, | 1179 | }, |
| 1179 | { | 1180 | { |
| 1180 | "id": "dd12bbb55060", | 1181 | "id": "dd12bbb55060", |
| @@ -1189,7 +1190,7 @@ | @@ -1189,7 +1190,7 @@ | ||
| 1189 | "likes": 0, | 1190 | "likes": 0, |
| 1190 | "createdAt": 1788013634333, | 1191 | "createdAt": 1788013634333, |
| 1191 | "promptEn": "glass bridge game, held the goal but paid a price", | 1192 | "promptEn": "glass bridge game, held the goal but paid a price", |
| 1192 | - "lastPlayedAt": 1788252773238 | 1193 | + "lastPlayedAt": 1788254355883 |
| 1193 | }, | 1194 | }, |
| 1194 | { | 1195 | { |
| 1195 | "id": "5b8ef21616d4", | 1196 | "id": "5b8ef21616d4", |
| @@ -1294,7 +1295,7 @@ | @@ -1294,7 +1295,7 @@ | ||
| 1294 | "likes": 0, | 1295 | "likes": 0, |
| 1295 | "createdAt": 1788012441458, | 1296 | "createdAt": 1788012441458, |
| 1296 | "promptEn": "facing Vecna, held the goal but paid a price", | 1297 | "promptEn": "facing Vecna, held the goal but paid a price", |
| 1297 | - "lastPlayedAt": 1788251798845 | 1298 | + "lastPlayedAt": 1788254283882 |
| 1298 | }, | 1299 | }, |
| 1299 | { | 1300 | { |
| 1300 | "id": "691c2f679759", | 1301 | "id": "691c2f679759", |
| @@ -1429,7 +1430,7 @@ | @@ -1429,7 +1430,7 @@ | ||
| 1429 | "likes": 0, | 1430 | "likes": 0, |
| 1430 | "createdAt": 1788010775613, | 1431 | "createdAt": 1788010775613, |
| 1431 | "promptEn": "facing Voldemort, stayed passive and got cornered", | 1432 | "promptEn": "facing Voldemort, stayed passive and got cornered", |
| 1432 | - "lastPlayedAt": 1788252633224 | 1433 | + "lastPlayedAt": 1788254061908 |
| 1433 | }, | 1434 | }, |
| 1434 | { | 1435 | { |
| 1435 | "id": "795842efa4a1", | 1436 | "id": "795842efa4a1", |
| @@ -1474,7 +1475,7 @@ | @@ -1474,7 +1475,7 @@ | ||
| 1474 | "likes": 0, | 1475 | "likes": 0, |
| 1475 | "createdAt": 1788010062535, | 1476 | "createdAt": 1788010062535, |
| 1476 | "promptEn": "facing Thanos, held the goal but paid a price", | 1477 | "promptEn": "facing Thanos, held the goal but paid a price", |
| 1477 | - "lastPlayedAt": 1788252076972 | 1478 | + "lastPlayedAt": 1788254091920 |
| 1478 | }, | 1479 | }, |
| 1479 | { | 1480 | { |
| 1480 | "id": "324b84ebc607", | 1481 | "id": "324b84ebc607", |
| @@ -1594,7 +1595,7 @@ | @@ -1594,7 +1595,7 @@ | ||
| 1594 | "likes": 0, | 1595 | "likes": 0, |
| 1595 | "createdAt": 1788008156307, | 1596 | "createdAt": 1788008156307, |
| 1596 | "promptEn": "hotel floor sealed by the Eternity Devil, an endless looping corridor", | 1597 | "promptEn": "hotel floor sealed by the Eternity Devil, an endless looping corridor", |
| 1597 | - "lastPlayedAt": 1788251948919 | 1598 | + "lastPlayedAt": 1788254119932 |
| 1598 | }, | 1599 | }, |
| 1599 | { | 1600 | { |
| 1600 | "id": "cd3b6dda90ad", | 1601 | "id": "cd3b6dda90ad", |
| @@ -1654,7 +1655,7 @@ | @@ -1654,7 +1655,7 @@ | ||
| 1654 | "likes": 0, | 1655 | "likes": 0, |
| 1655 | "createdAt": 1788007201908, | 1656 | "createdAt": 1788007201908, |
| 1656 | "promptEn": "breaking into the palace, Neferpitou stalling to protect the king", | 1657 | "promptEn": "breaking into the palace, Neferpitou stalling to protect the king", |
| 1657 | - "lastPlayedAt": 1788252681196 | 1658 | + "lastPlayedAt": 1788254139946 |
| 1658 | }, | 1659 | }, |
| 1659 | { | 1660 | { |
| 1660 | "id": "628026375795", | 1661 | "id": "628026375795", |
| @@ -1699,7 +1700,7 @@ | @@ -1699,7 +1700,7 @@ | ||
| 1699 | "likes": 0, | 1700 | "likes": 0, |
| 1700 | "createdAt": 1788006252918, | 1701 | "createdAt": 1788006252918, |
| 1701 | "promptEn": "facing L, stayed passive and the evidence pressure maxed out", | 1702 | "promptEn": "facing L, stayed passive and the evidence pressure maxed out", |
| 1702 | - "lastPlayedAt": 1788253113430 | 1703 | + "lastPlayedAt": 1788254350039 |
| 1703 | }, | 1704 | }, |
| 1704 | { | 1705 | { |
| 1705 | "id": "0e28a7fecf03", | 1706 | "id": "0e28a7fecf03", |
| @@ -1939,7 +1940,7 @@ | @@ -1939,7 +1940,7 @@ | ||
| 1939 | "likes": 0, | 1940 | "likes": 0, |
| 1940 | "createdAt": 1788002929455, | 1941 | "createdAt": 1788002929455, |
| 1941 | "promptEn": "facing Reiner, exploiting a flaw to counter him", | 1942 | "promptEn": "facing Reiner, exploiting a flaw to counter him", |
| 1942 | - "lastPlayedAt": 1788252195030 | 1943 | + "lastPlayedAt": 1788254288010 |
| 1943 | }, | 1944 | }, |
| 1944 | { | 1945 | { |
| 1945 | "id": "ea8b38014674", | 1946 | "id": "ea8b38014674", |
| @@ -1954,7 +1955,7 @@ | @@ -1954,7 +1955,7 @@ | ||
| 1954 | "likes": 0, | 1955 | "likes": 0, |
| 1955 | "createdAt": 1788002691822, | 1956 | "createdAt": 1788002691822, |
| 1956 | "promptEn": "facing Reiner, held the goal but paid a price", | 1957 | "promptEn": "facing Reiner, held the goal but paid a price", |
| 1957 | - "lastPlayedAt": 1788252655237 | 1958 | + "lastPlayedAt": 1788254037898 |
| 1958 | }, | 1959 | }, |
| 1959 | { | 1960 | { |
| 1960 | "id": "f65b15d67966", | 1961 | "id": "f65b15d67966", |
| @@ -2059,7 +2060,7 @@ | @@ -2059,7 +2060,7 @@ | ||
| 2059 | "likes": 0, | 2060 | "likes": 0, |
| 2060 | "createdAt": 1788001049233, | 2061 | "createdAt": 1788001049233, |
| 2061 | "promptEn": "facing Pain, held the goal but paid a price", | 2062 | "promptEn": "facing Pain, held the goal but paid a price", |
| 2062 | - "lastPlayedAt": 1788251780839 | 2063 | + "lastPlayedAt": 1788254115931 |
| 2063 | }, | 2064 | }, |
| 2064 | { | 2065 | { |
| 2065 | "id": "7ecd480a3c31", | 2066 | "id": "7ecd480a3c31", |
| @@ -2224,7 +2225,7 @@ | @@ -2224,7 +2225,7 @@ | ||
| 2224 | "likes": 0, | 2225 | "likes": 0, |
| 2225 | "createdAt": 1787900324972, | 2226 | "createdAt": 1787900324972, |
| 2226 | "promptEn": "constable declines a drink, asks what this place is, she watches him through the screen", | 2227 | "promptEn": "constable declines a drink, asks what this place is, she watches him through the screen", |
| 2227 | - "lastPlayedAt": 1788252709206 | 2228 | + "lastPlayedAt": 1788254371882 |
| 2228 | }, | 2229 | }, |
| 2229 | { | 2230 | { |
| 2230 | "id": "43ee5c728f1b", | 2231 | "id": "43ee5c728f1b", |
| @@ -2434,7 +2435,7 @@ | @@ -2434,7 +2435,7 @@ | ||
| 2434 | "likes": 0, | 2435 | "likes": 0, |
| 2435 | "createdAt": 1787708713767, | 2436 | "createdAt": 1787708713767, |
| 2436 | "promptEn": "constable stops looking around, turns to face the screen, speaks to her silhouette", | 2437 | "promptEn": "constable stops looking around, turns to face the screen, speaks to her silhouette", |
| 2437 | - "lastPlayedAt": 1788252811258 | 2438 | + "lastPlayedAt": 1788254322029 |
| 2438 | }, | 2439 | }, |
| 2439 | { | 2440 | { |
| 2440 | "id": "5dc95a15cd5d", | 2441 | "id": "5dc95a15cd5d", |
| @@ -2464,7 +2465,7 @@ | @@ -2464,7 +2465,7 @@ | ||
| 2464 | "likes": 0, | 2465 | "likes": 0, |
| 2465 | "createdAt": 1787708686397, | 2466 | "createdAt": 1787708686397, |
| 2466 | "promptEn": "constable accepts the invitation, moves to sit on a stool closer to the screen", | 2467 | "promptEn": "constable accepts the invitation, moves to sit on a stool closer to the screen", |
| 2467 | - "lastPlayedAt": 1788248577063 | 2468 | + "lastPlayedAt": 1788254145947 |
| 2468 | }, | 2469 | }, |
| 2469 | { | 2470 | { |
| 2470 | "id": "ad611bcbd06b", | 2471 | "id": "ad611bcbd06b", |
| @@ -2539,7 +2540,7 @@ | @@ -2539,7 +2540,7 @@ | ||
| 2539 | "likes": 0, | 2540 | "likes": 0, |
| 2540 | "createdAt": 1787708648409, | 2541 | "createdAt": 1787708648409, |
| 2541 | "promptEn": "she reaches a hand through the screen gap beckoning him, he steps back and firmly refuses", | 2542 | "promptEn": "she reaches a hand through the screen gap beckoning him, he steps back and firmly refuses", |
| 2542 | - "lastPlayedAt": 1788251828861 | 2543 | + "lastPlayedAt": 1788254125936 |
| 2543 | }, | 2544 | }, |
| 2544 | { | 2545 | { |
| 2545 | "id": "3f0c28f5373a", | 2546 | "id": "3f0c28f5373a", |
| @@ -2749,7 +2750,7 @@ | @@ -2749,7 +2750,7 @@ | ||
| 2749 | "likes": 0, | 2750 | "likes": 0, |
| 2750 | "createdAt": 1787674688717, | 2751 | "createdAt": 1787674688717, |
| 2751 | "promptEn": "constable agrees to stay, lets his guard down, she gives a barely-there nod", | 2752 | "promptEn": "constable agrees to stay, lets his guard down, she gives a barely-there nod", |
| 2752 | - "lastPlayedAt": 1788250832261 | 2753 | + "lastPlayedAt": 1788254161950 |
| 2753 | }, | 2754 | }, |
| 2754 | { | 2755 | { |
| 2755 | "id": "4874fe3fdbc9", | 2756 | "id": "4874fe3fdbc9", |
| @@ -2779,7 +2780,7 @@ | @@ -2779,7 +2780,7 @@ | ||
| 2779 | "likes": 0, | 2780 | "likes": 0, |
| 2780 | "createdAt": 1787674642139, | 2781 | "createdAt": 1787674642139, |
| 2781 | "promptEn": "constable forms hand seals and summons a giant toad out of smoke", | 2782 | "promptEn": "constable forms hand seals and summons a giant toad out of smoke", |
| 2782 | - "lastPlayedAt": 1788250076972 | 2783 | + "lastPlayedAt": 1788254251995 |
| 2783 | }, | 2784 | }, |
| 2784 | { | 2785 | { |
| 2785 | "id": "e9a6320e18a9", | 2786 | "id": "e9a6320e18a9", |
| @@ -2854,7 +2855,7 @@ | @@ -2854,7 +2855,7 @@ | ||
| 2854 | "likes": 0, | 2855 | "likes": 0, |
| 2855 | "createdAt": 1787674446841, | 2856 | "createdAt": 1787674446841, |
| 2856 | "promptEn": "player panics into the room, glances back, out of breath, says something's chasing him outside", | 2857 | "promptEn": "player panics into the room, glances back, out of breath, says something's chasing him outside", |
| 2857 | - "lastPlayedAt": 1788250856275 | 2858 | + "lastPlayedAt": 1788254185961 |
| 2858 | }, | 2859 | }, |
| 2859 | { | 2860 | { |
| 2860 | "id": "c25d53a5e086", | 2861 | "id": "c25d53a5e086", |
| @@ -2869,7 +2870,7 @@ | @@ -2869,7 +2870,7 @@ | ||
| 2869 | "likes": 0, | 2870 | "likes": 0, |
| 2870 | "createdAt": 1787674395006, | 2871 | "createdAt": 1787674395006, |
| 2871 | "promptEn": "constable and the woman hold a tense silent stare through the screen", | 2872 | "promptEn": "constable and the woman hold a tense silent stare through the screen", |
| 2872 | - "lastPlayedAt": 1788252897296 | 2873 | + "lastPlayedAt": 1788254099923 |
| 2873 | }, | 2874 | }, |
| 2874 | { | 2875 | { |
| 2875 | "id": "f264bce22903", | 2876 | "id": "f264bce22903", |
| @@ -2974,7 +2975,7 @@ | @@ -2974,7 +2975,7 @@ | ||
| 2974 | "likes": 0, | 2975 | "likes": 0, |
| 2975 | "createdAt": 1787640567001, | 2976 | "createdAt": 1787640567001, |
| 2976 | "promptEn": "summoned toad Wentai, beat the painted-skin female ghost", | 2977 | "promptEn": "summoned toad Wentai, beat the painted-skin female ghost", |
| 2977 | - "lastPlayedAt": 1788252685196 | 2978 | + "lastPlayedAt": 1788254552120 |
| 2978 | }, | 2979 | }, |
| 2979 | { | 2980 | { |
| 2980 | "id": "1cc42a8669a1", | 2981 | "id": "1cc42a8669a1", |
| @@ -3004,7 +3005,7 @@ | @@ -3004,7 +3005,7 @@ | ||
| 3004 | "likes": 0, | 3005 | "likes": 0, |
| 3005 | "createdAt": 1787637292741, | 3006 | "createdAt": 1787637292741, |
| 3006 | "promptEn": "constable bursts in panicked, spots the woman behind the screen", | 3007 | "promptEn": "constable bursts in panicked, spots the woman behind the screen", |
| 3007 | - "lastPlayedAt": 1788251872881 | 3008 | + "lastPlayedAt": 1788254566123 |
| 3008 | }, | 3009 | }, |
| 3009 | { | 3010 | { |
| 3010 | "id": "93c2b84a2a10", | 3011 | "id": "93c2b84a2a10", |
| @@ -3139,7 +3140,7 @@ | @@ -3139,7 +3140,7 @@ | ||
| 3139 | "likes": 0, | 3140 | "likes": 0, |
| 3140 | "createdAt": 1787588545801, | 3141 | "createdAt": 1787588545801, |
| 3141 | "promptEn": "game menu UI, character swaps out a mechanical arm", | 3142 | "promptEn": "game menu UI, character swaps out a mechanical arm", |
| 3142 | - "lastPlayedAt": 1788249159434 | 3143 | + "lastPlayedAt": 1788254406058 |
| 3143 | }, | 3144 | }, |
| 3144 | { | 3145 | { |
| 3145 | "id": "fe749aa533a0", | 3146 | "id": "fe749aa533a0", |
| @@ -3184,7 +3185,7 @@ | @@ -3184,7 +3185,7 @@ | ||
| 3184 | "likes": 0, | 3185 | "likes": 0, |
| 3185 | "createdAt": 1787588544109, | 3186 | "createdAt": 1787588544109, |
| 3186 | "promptEn": "vampire romance drama trailer, girl wanders into the castle", | 3187 | "promptEn": "vampire romance drama trailer, girl wanders into the castle", |
| 3187 | - "lastPlayedAt": 1788250187972 | 3188 | + "lastPlayedAt": 1788254272005 |
| 3188 | }, | 3189 | }, |
| 3189 | { | 3190 | { |
| 3190 | "id": "7ded7db676d3", | 3191 | "id": "7ded7db676d3", |
| @@ -3199,7 +3200,7 @@ | @@ -3199,7 +3200,7 @@ | ||
| 3199 | "likes": 0, | 3200 | "likes": 0, |
| 3200 | "createdAt": 1787588543551, | 3201 | "createdAt": 1787588543551, |
| 3201 | "promptEn": "MiniMax H3 tech brand film, blue waveform logo", | 3202 | "promptEn": "MiniMax H3 tech brand film, blue waveform logo", |
| 3202 | - "lastPlayedAt": 1788250954321 | 3203 | + "lastPlayedAt": 1788254047898 |
| 3203 | }, | 3204 | }, |
| 3204 | { | 3205 | { |
| 3205 | "id": "81711242178d", | 3206 | "id": "81711242178d", |
| @@ -3244,7 +3245,7 @@ | @@ -3244,7 +3245,7 @@ | ||
| 3244 | "likes": 0, | 3245 | "likes": 0, |
| 3245 | "createdAt": 1787588541726, | 3246 | "createdAt": 1787588541726, |
| 3246 | "promptEn": "woman opens the trunk on a desert road, takes out a bag", | 3247 | "promptEn": "woman opens the trunk on a desert road, takes out a bag", |
| 3247 | - "lastPlayedAt": 1788251984934 | 3248 | + "lastPlayedAt": 1788254335882 |
| 3248 | }, | 3249 | }, |
| 3249 | { | 3250 | { |
| 3250 | "id": "97846542ab09", | 3251 | "id": "97846542ab09", |
| @@ -3514,7 +3515,7 @@ | @@ -3514,7 +3515,7 @@ | ||
| 3514 | "likes": 0, | 3515 | "likes": 0, |
| 3515 | "createdAt": 1787573698485, | 3516 | "createdAt": 1787573698485, |
| 3516 | "promptEn": "underground rap duo freestyling at the camera", | 3517 | "promptEn": "underground rap duo freestyling at the camera", |
| 3517 | - "lastPlayedAt": 1788252777241 | 3518 | + "lastPlayedAt": 1788254075916 |
| 3518 | }, | 3519 | }, |
| 3519 | { | 3520 | { |
| 3520 | "id": "e5246eae814e", | 3521 | "id": "e5246eae814e", |
| @@ -3543,7 +3544,7 @@ | @@ -3543,7 +3544,7 @@ | ||
| 3543 | "partition": "text", | 3544 | "partition": "text", |
| 3544 | "likes": 0, | 3545 | "likes": 0, |
| 3545 | "createdAt": 1787568512602, | 3546 | "createdAt": 1787568512602, |
| 3546 | - "lastPlayedAt": 1788252082974, | 3547 | + "lastPlayedAt": 1788254065909, |
| 3547 | "promptEn": "man apologizes, says something outside was chasing him" | 3548 | "promptEn": "man apologizes, says something outside was chasing him" |
| 3548 | }, | 3549 | }, |
| 3549 | { | 3550 | { |
| @@ -3664,7 +3665,7 @@ | @@ -3664,7 +3665,7 @@ | ||
| 3664 | "likes": 0, | 3665 | "likes": 0, |
| 3665 | "createdAt": 1787470101411, | 3666 | "createdAt": 1787470101411, |
| 3666 | "promptEn": "vertical family-fight drama, arguing with an older woman", | 3667 | "promptEn": "vertical family-fight drama, arguing with an older woman", |
| 3667 | - "lastPlayedAt": 1788251276478 | 3668 | + "lastPlayedAt": 1788254011880 |
| 3668 | }, | 3669 | }, |
| 3669 | { | 3670 | { |
| 3670 | "id": "57169e6751d0", | 3671 | "id": "57169e6751d0", |
| @@ -3754,7 +3755,7 @@ | @@ -3754,7 +3755,7 @@ | ||
| 3754 | "likes": 0, | 3755 | "likes": 0, |
| 3755 | "createdAt": 1787470099306, | 3756 | "createdAt": 1787470099306, |
| 3756 | "promptEn": "late-night laundromat footage mixed with glowing hand-drawn animation", | 3757 | "promptEn": "late-night laundromat footage mixed with glowing hand-drawn animation", |
| 3757 | - "lastPlayedAt": 1788251502656 | 3758 | + "lastPlayedAt": 1788254294016 |
| 3758 | }, | 3759 | }, |
| 3759 | { | 3760 | { |
| 3760 | "id": "c0e23beb4132", | 3761 | "id": "c0e23beb4132", |
| @@ -3814,7 +3815,7 @@ | @@ -3814,7 +3815,7 @@ | ||
| 3814 | "likes": 0, | 3815 | "likes": 0, |
| 3815 | "createdAt": 1787455895150, | 3816 | "createdAt": 1787455895150, |
| 3816 | "promptEn": "protagonist leaps across rooftops fleeing pursuers", | 3817 | "promptEn": "protagonist leaps across rooftops fleeing pursuers", |
| 3817 | - "lastPlayedAt": 1788252253059 | 3818 | + "lastPlayedAt": 1788254418063 |
| 3818 | }, | 3819 | }, |
| 3819 | { | 3820 | { |
| 3820 | "id": "2a4e45820135", | 3821 | "id": "2a4e45820135", |
| @@ -4054,7 +4055,7 @@ | @@ -4054,7 +4055,7 @@ | ||
| 4054 | "likes": 0, | 4055 | "likes": 0, |
| 4055 | "createdAt": 1787413511959, | 4056 | "createdAt": 1787413511959, |
| 4056 | "promptEn": "protagonist leaps across rooftops fleeing pursuers", | 4057 | "promptEn": "protagonist leaps across rooftops fleeing pursuers", |
| 4057 | - "lastPlayedAt": 1788251161159 | 4058 | + "lastPlayedAt": 1788254235982 |
| 4058 | }, | 4059 | }, |
| 4059 | { | 4060 | { |
| 4060 | "id": "5d4f40bf0889", | 4061 | "id": "5d4f40bf0889", |
| @@ -4099,7 +4100,7 @@ | @@ -4099,7 +4100,7 @@ | ||
| 4099 | "likes": 0, | 4100 | "likes": 0, |
| 4100 | "createdAt": 1787412150062, | 4101 | "createdAt": 1787412150062, |
| 4101 | "promptEn": "protagonist leaps across rooftops fleeing pursuers", | 4102 | "promptEn": "protagonist leaps across rooftops fleeing pursuers", |
| 4102 | - "lastPlayedAt": 1788252809259 | 4103 | + "lastPlayedAt": 1788254542110 |
| 4103 | }, | 4104 | }, |
| 4104 | { | 4105 | { |
| 4105 | "id": "9d2486d6f969", | 4106 | "id": "9d2486d6f969", |
| @@ -4129,7 +4130,7 @@ | @@ -4129,7 +4130,7 @@ | ||
| 4129 | "likes": 0, | 4130 | "likes": 0, |
| 4130 | "createdAt": 1787388090646, | 4131 | "createdAt": 1787388090646, |
| 4131 | "promptEn": "a cat sitting on a windowsill", | 4132 | "promptEn": "a cat sitting on a windowsill", |
| 4132 | - "lastPlayedAt": 1788252393130 | 4133 | + "lastPlayedAt": 1788254205967 |
| 4133 | }, | 4134 | }, |
| 4134 | { | 4135 | { |
| 4135 | "id": "c551457bc55e", | 4136 | "id": "c551457bc55e", |
| @@ -4159,7 +4160,7 @@ | @@ -4159,7 +4160,7 @@ | ||
| 4159 | "likes": 0, | 4160 | "likes": 0, |
| 4160 | "createdAt": 1787384983124, | 4161 | "createdAt": 1787384983124, |
| 4161 | "promptEn": "protagonist leaps across rooftops fleeing pursuers", | 4162 | "promptEn": "protagonist leaps across rooftops fleeing pursuers", |
| 4162 | - "lastPlayedAt": 1788251892892 | 4163 | + "lastPlayedAt": 1788254354037 |
| 4163 | }, | 4164 | }, |
| 4164 | { | 4165 | { |
| 4165 | "id": "7753a046ba79", | 4166 | "id": "7753a046ba79", |
| @@ -4294,7 +4295,7 @@ | @@ -4294,7 +4295,7 @@ | ||
| 4294 | "likes": 0, | 4295 | "likes": 0, |
| 4295 | "createdAt": 1787367453839, | 4296 | "createdAt": 1787367453839, |
| 4296 | "promptEn": "golden portal opens, man in green jacket throws a glowing spear", | 4297 | "promptEn": "golden portal opens, man in green jacket throws a glowing spear", |
| 4297 | - "lastPlayedAt": 1788252006941 | 4298 | + "lastPlayedAt": 1788254041895 |
| 4298 | }, | 4299 | }, |
| 4299 | { | 4300 | { |
| 4300 | "id": "8402adae59dc", | 4301 | "id": "8402adae59dc", |
| @@ -4309,7 +4310,7 @@ | @@ -4309,7 +4310,7 @@ | ||
| 4309 | "likes": 0, | 4310 | "likes": 0, |
| 4310 | "createdAt": 1787363956909, | 4311 | "createdAt": 1787363956909, |
| 4311 | "promptEn": "protagonist leaps across rooftops fleeing pursuers", | 4312 | "promptEn": "protagonist leaps across rooftops fleeing pursuers", |
| 4312 | - "lastPlayedAt": 1788250706222 | 4313 | + "lastPlayedAt": 1788254362044 |
| 4313 | }, | 4314 | }, |
| 4314 | { | 4315 | { |
| 4315 | "id": "5692965402f5", | 4316 | "id": "5692965402f5", |
| @@ -4399,7 +4400,7 @@ | @@ -4399,7 +4400,7 @@ | ||
| 4399 | "likes": 0, | 4400 | "likes": 0, |
| 4400 | "createdAt": 1787309864037, | 4401 | "createdAt": 1787309864037, |
| 4401 | "promptEn": "transparent gaming mouse product film, blue and orange lighting", | 4402 | "promptEn": "transparent gaming mouse product film, blue and orange lighting", |
| 4402 | - "lastPlayedAt": 1788252567195 | 4403 | + "lastPlayedAt": 1788254219979 |
| 4403 | }, | 4404 | }, |
| 4404 | { | 4405 | { |
| 4405 | "id": "073f5df6427a", | 4406 | "id": "073f5df6427a", |
| @@ -4549,7 +4550,7 @@ | @@ -4549,7 +4550,7 @@ | ||
| 4549 | "likes": 0, | 4550 | "likes": 0, |
| 4550 | "createdAt": 1787225897650, | 4551 | "createdAt": 1787225897650, |
| 4551 | "promptEn": "constable burns the beauty portrait with the lamp, the door opens on its own", | 4552 | "promptEn": "constable burns the beauty portrait with the lamp, the door opens on its own", |
| 4552 | - "lastPlayedAt": 1788253107430 | 4553 | + "lastPlayedAt": 1788254544113 |
| 4553 | }, | 4554 | }, |
| 4554 | { | 4555 | { |
| 4555 | "id": "ed3663dcda7f", | 4556 | "id": "ed3663dcda7f", |
server.js
| @@ -175,6 +175,7 @@ function markPlayed(id) { | @@ -175,6 +175,7 @@ function markPlayed(id) { | ||
| 175 | const clip = lib.find((c) => c.id === id); | 175 | const clip = lib.find((c) => c.id === id); |
| 176 | if (!clip) return; | 176 | if (!clip) return; |
| 177 | clip.lastPlayedAt = Date.now(); | 177 | clip.lastPlayedAt = Date.now(); |
| 178 | + clip.playCount = (clip.playCount || 0) + 1; | ||
| 178 | saveLibrary(lib); | 179 | saveLibrary(lib); |
| 179 | } | 180 | } |
| 180 | 181 | ||
| @@ -225,7 +226,11 @@ function pickNextClip() { | @@ -225,7 +226,11 @@ function pickNextClip() { | ||
| 225 | // random among everything that isn't on cooldown, no bias toward "fresh". | 226 | // random among everything that isn't on cooldown, no bias toward "fresh". |
| 226 | const offCooldown = notPrev.filter((c) => !c.lastPlayedAt || now - c.lastPlayedAt > REPEAT_COOLDOWN_MS); | 227 | const offCooldown = notPrev.filter((c) => !c.lastPlayedAt || now - c.lastPlayedAt > REPEAT_COOLDOWN_MS); |
| 227 | const pool = offCooldown.length ? offCooldown : notPrev; | 228 | const pool = offCooldown.length ? offCooldown : notPrev; |
| 228 | - return { clip: pool[Math.floor(Math.random() * pool.length)], requestedBy: null, chatId: null }; | 229 | + // Within that pool, only randomize among the least-played clips — keeps |
| 230 | + // rotation even instead of letting a few clips dominate airtime by chance. | ||
| 231 | + const minPlays = Math.min(...pool.map((c) => c.playCount || 0)); | ||
| 232 | + const leastPlayed = pool.filter((c) => (c.playCount || 0) === minPlays); | ||
| 233 | + return { clip: leastPlayed[Math.floor(Math.random() * leastPlayed.length)], requestedBy: null, chatId: null }; | ||
| 229 | } | 234 | } |
| 230 | 235 | ||
| 231 | const FAILED_ENTRY_TTL_MS = 20000; // how long a "generation failed" entry stays visible before cleanup | 236 | const FAILED_ENTRY_TTL_MS = 20000; // how long a "generation failed" entry stays visible before cleanup |