Commit edfe6543d392231836d45c98040351895ae4868b

Authored by yibo
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