more controls, metadata
This commit is contained in:
parent
3783f0fa3a
commit
7e4038386e
|
@ -847,11 +847,19 @@
|
|||
"version": "7.7.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.7.tgz",
|
||||
"integrity": "sha512-uCnC2JEVAu8AKB5do1WRIsvrdJ0flYx/A/9f/6chdacnEZ7LmavjdsDXr5ksYBegxtuTPR5Va9/+13QF/kFkCA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.2"
|
||||
}
|
||||
},
|
||||
"@babel/runtime-corejs2": {
|
||||
"version": "7.7.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime-corejs2/-/runtime-corejs2-7.7.7.tgz",
|
||||
"integrity": "sha512-P91T3dFYQL7aj44PxOMIAbo66Ag3NbmXG9fseSYaXxapp3K9XTct5HU9IpTOm2D0AoktKusgqzN5YcSxZXEKBQ==",
|
||||
"requires": {
|
||||
"core-js": "^2.6.5",
|
||||
"regenerator-runtime": "^0.13.2"
|
||||
}
|
||||
},
|
||||
"@babel/runtime-corejs3": {
|
||||
"version": "7.7.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.7.7.tgz",
|
||||
|
@ -1014,6 +1022,14 @@
|
|||
"integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==",
|
||||
"dev": true
|
||||
},
|
||||
"agent-base": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz",
|
||||
"integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==",
|
||||
"requires": {
|
||||
"es6-promisify": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"ajv": {
|
||||
"version": "6.10.2",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
|
||||
|
@ -1081,11 +1097,15 @@
|
|||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"argv": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/argv/-/argv-0.0.2.tgz",
|
||||
"integrity": "sha1-7L0W+JSbFXGDcRsb2jNPN4QBhas="
|
||||
},
|
||||
"arr-diff": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
|
||||
|
@ -1272,8 +1292,7 @@
|
|||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
||||
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
|
||||
"dev": true
|
||||
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
|
||||
},
|
||||
"base": {
|
||||
"version": "0.11.2",
|
||||
|
@ -1376,7 +1395,6 @@
|
|||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
|
@ -1761,6 +1779,18 @@
|
|||
"q": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"codecov": {
|
||||
"version": "3.6.1",
|
||||
"resolved": "https://registry.npmjs.org/codecov/-/codecov-3.6.1.tgz",
|
||||
"integrity": "sha512-IUJB6WG47nWK7o50etF8jBadxdMw7DmoQg05yIljstXFBGB6clOZsIj6iD4P82T2YaIU3qq+FFu8K9pxgkCJDQ==",
|
||||
"requires": {
|
||||
"argv": "^0.0.2",
|
||||
"ignore-walk": "^3.0.1",
|
||||
"js-yaml": "^3.13.1",
|
||||
"teeny-request": "^3.11.3",
|
||||
"urlgrey": "^0.4.4"
|
||||
}
|
||||
},
|
||||
"collection-visit": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
|
||||
|
@ -1836,8 +1866,7 @@
|
|||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
|
||||
"dev": true
|
||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
|
||||
},
|
||||
"concat-stream": {
|
||||
"version": "1.6.2",
|
||||
|
@ -1881,8 +1910,7 @@
|
|||
"core-js": {
|
||||
"version": "2.6.11",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz",
|
||||
"integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==",
|
||||
"dev": true
|
||||
"integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg=="
|
||||
},
|
||||
"core-js-compat": {
|
||||
"version": "3.6.2",
|
||||
|
@ -2662,6 +2690,19 @@
|
|||
"is-symbol": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"es6-promise": {
|
||||
"version": "4.2.8",
|
||||
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
|
||||
"integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w=="
|
||||
},
|
||||
"es6-promisify": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz",
|
||||
"integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=",
|
||||
"requires": {
|
||||
"es6-promise": "^4.0.3"
|
||||
}
|
||||
},
|
||||
"escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
|
@ -3949,6 +3990,25 @@
|
|||
"integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=",
|
||||
"dev": true
|
||||
},
|
||||
"https-proxy-agent": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz",
|
||||
"integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==",
|
||||
"requires": {
|
||||
"agent-base": "^4.3.0",
|
||||
"debug": "^3.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "3.2.6",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
|
||||
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
|
@ -3970,6 +4030,14 @@
|
|||
"integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==",
|
||||
"dev": true
|
||||
},
|
||||
"ignore-walk": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz",
|
||||
"integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==",
|
||||
"requires": {
|
||||
"minimatch": "^3.0.4"
|
||||
}
|
||||
},
|
||||
"import-fresh": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz",
|
||||
|
@ -4286,6 +4354,29 @@
|
|||
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=",
|
||||
"dev": true
|
||||
},
|
||||
"iter-tools": {
|
||||
"version": "7.0.0-rc.0",
|
||||
"resolved": "https://registry.npmjs.org/iter-tools/-/iter-tools-7.0.0-rc.0.tgz",
|
||||
"integrity": "sha512-u5pdY3kERasb9eUiAuVIWoYSpUnKKv7xXol+KpwIT+U+/LUDT+F1xTPdSNLHcNxIxAcjIWG+Adu6jtkgCOKd4Q==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.4.3",
|
||||
"@babel/runtime-corejs2": "^7.4.3",
|
||||
"codecov": "^3.6.1",
|
||||
"decamelize": "^3.2.0",
|
||||
"little-ds-toolkit": "^1.1.0",
|
||||
"typescript-tuple": "^2.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"decamelize": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-3.2.0.tgz",
|
||||
"integrity": "sha512-4TgkVUsmmu7oCSyGBm5FvfMoACuoh9EOidm7V5/J2X2djAwwt57qb3F2KMP2ITqODTCSwb+YRV+0Zqrv18k/hw==",
|
||||
"requires": {
|
||||
"xregexp": "^4.2.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"js-levenshtein": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz",
|
||||
|
@ -4302,7 +4393,6 @@
|
|||
"version": "3.13.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
|
||||
"integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"argparse": "^1.0.7",
|
||||
"esprima": "^4.0.0"
|
||||
|
@ -4311,8 +4401,7 @@
|
|||
"esprima": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
|
||||
"dev": true
|
||||
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -4453,6 +4542,11 @@
|
|||
"type-check": "~0.3.2"
|
||||
}
|
||||
},
|
||||
"little-ds-toolkit": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/little-ds-toolkit/-/little-ds-toolkit-1.1.1.tgz",
|
||||
"integrity": "sha512-Zl5flhnd5W6nhRCyoL1bNlU8M5CWFp6SItMmK4pj39LKgzQD7LGg591OJ0jwDKat7mjHvJVkOyJT+BXOQH4iXw=="
|
||||
},
|
||||
"load-script2": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/load-script2/-/load-script2-2.0.4.tgz",
|
||||
|
@ -4654,7 +4748,6 @@
|
|||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
|
@ -4706,8 +4799,7 @@
|
|||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"dev": true
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
},
|
||||
"nan": {
|
||||
"version": "2.14.0",
|
||||
|
@ -4747,6 +4839,11 @@
|
|||
"integrity": "sha512-2+DuKodWvwRTrCfKOeR24KIc5unKjOh8mz17NCzVnHWfjAdDqbfbjqh7gUT+BkXBRQM52+xCHciKWonJ3CbJMQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node-fetch": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz",
|
||||
"integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA=="
|
||||
},
|
||||
"node-forge": {
|
||||
"version": "0.7.6",
|
||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.6.tgz",
|
||||
|
@ -6514,8 +6611,7 @@
|
|||
"sprintf-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
|
||||
"dev": true
|
||||
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
|
||||
},
|
||||
"sshpk": {
|
||||
"version": "1.16.1",
|
||||
|
@ -6780,6 +6876,16 @@
|
|||
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
|
||||
"dev": true
|
||||
},
|
||||
"teeny-request": {
|
||||
"version": "3.11.3",
|
||||
"resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-3.11.3.tgz",
|
||||
"integrity": "sha512-CKncqSF7sH6p4rzCgkb/z/Pcos5efl0DmolzvlqRQUNcpRIruOhY9+T1FsIlyEbfWd7MsFpodROOwHYh2BaXzw==",
|
||||
"requires": {
|
||||
"https-proxy-agent": "^2.2.1",
|
||||
"node-fetch": "^2.2.0",
|
||||
"uuid": "^3.3.2"
|
||||
}
|
||||
},
|
||||
"terser": {
|
||||
"version": "3.17.0",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-3.17.0.tgz",
|
||||
|
@ -6942,6 +7048,27 @@
|
|||
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
|
||||
"dev": true
|
||||
},
|
||||
"typescript-compare": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript-compare/-/typescript-compare-0.0.2.tgz",
|
||||
"integrity": "sha512-8ja4j7pMHkfLJQO2/8tut7ub+J3Lw2S3061eJLFQcvs3tsmJKp8KG5NtpLn7KcY2w08edF74BSVN7qJS0U6oHA==",
|
||||
"requires": {
|
||||
"typescript-logic": "^0.0.0"
|
||||
}
|
||||
},
|
||||
"typescript-logic": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/typescript-logic/-/typescript-logic-0.0.0.tgz",
|
||||
"integrity": "sha512-zXFars5LUkI3zP492ls0VskH3TtdeHCqu0i7/duGt60i5IGPIpAHE/DWo5FqJ6EjQ15YKXrt+AETjv60Dat34Q=="
|
||||
},
|
||||
"typescript-tuple": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/typescript-tuple/-/typescript-tuple-2.2.1.tgz",
|
||||
"integrity": "sha512-Zcr0lbt8z5ZdEzERHAMAniTiIKerFCMgd7yjq1fPnDJ43et/k9twIFQMUYff9k5oXcsQ0WpvFcgzK2ZKASoW6Q==",
|
||||
"requires": {
|
||||
"typescript-compare": "^0.0.2"
|
||||
}
|
||||
},
|
||||
"uncss": {
|
||||
"version": "0.17.2",
|
||||
"resolved": "https://registry.npmjs.org/uncss/-/uncss-0.17.2.tgz",
|
||||
|
@ -7140,6 +7267,11 @@
|
|||
"tlds": "^1.203.0"
|
||||
}
|
||||
},
|
||||
"urlgrey": {
|
||||
"version": "0.4.4",
|
||||
"resolved": "https://registry.npmjs.org/urlgrey/-/urlgrey-0.4.4.tgz",
|
||||
"integrity": "sha1-iS/pWWCAXoVRnxzUOJ8stMu3ZS8="
|
||||
},
|
||||
"use": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
|
||||
|
@ -7182,8 +7314,7 @@
|
|||
"uuid": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz",
|
||||
"integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ=="
|
||||
},
|
||||
"v8-compile-cache": {
|
||||
"version": "2.1.0",
|
||||
|
@ -7357,6 +7488,14 @@
|
|||
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
|
||||
"dev": true
|
||||
},
|
||||
"xregexp": {
|
||||
"version": "4.2.4",
|
||||
"resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.2.4.tgz",
|
||||
"integrity": "sha512-sO0bYdYeJAJBcJA8g7MJJX7UrOZIfJPd8U2SC7B2Dd/J24U0aQNoGp33shCaBSWeb0rD5rh6VBUIXOkGal1TZA==",
|
||||
"requires": {
|
||||
"@babel/runtime-corejs2": "^7.2.0"
|
||||
}
|
||||
},
|
||||
"xtend": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
"@babel/runtime-corejs3": "^7.7.7",
|
||||
"date-fns": "^2.9.0",
|
||||
"get-urls": "^9.2.0",
|
||||
"iter-tools": "^7.0.0-rc.0",
|
||||
"yt-player": "^3.4.3"
|
||||
},
|
||||
"browserslist": [
|
||||
|
|
129
src/App.svelte
129
src/App.svelte
|
@ -1,129 +0,0 @@
|
|||
<main class="app">
|
||||
<header class="header">
|
||||
<h1>
|
||||
Eldritch Radio
|
||||
</h1>
|
||||
</header>
|
||||
|
||||
<section class="player">
|
||||
{#if $selectedEntry}
|
||||
Playing <a href={$selectedEntry.url}>{$selectedEntry.id}</a>
|
||||
<YoutubeViewer bind:videoId={$selectedEntry.id} bind:playing={$playing}></YoutubeViewer>
|
||||
{:else}
|
||||
Loading ...
|
||||
{/if}
|
||||
|
||||
<div>
|
||||
<button>⏮️</button>
|
||||
<button on:click={() => $playing = !$playing}>{#if $playing}⏸️{:else}▶️{/if}</button>
|
||||
<button on:click={() => selectedEntry.next()}>⏭️</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button>🔇 / 🔊</button>
|
||||
<input type="range" min="0" max="100" value="80">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button>⭐</button>
|
||||
<button>🔁</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="queue">
|
||||
<div>
|
||||
{#if $entries}
|
||||
<ul>
|
||||
{#each $entries as entry}
|
||||
<li class="entry" class:active={entry === $selectedEntry} on:click={() => $selectedEntry = entry}>
|
||||
<div>{entry.url}</div>
|
||||
<b>{entry.status.account.acct}</b>
|
||||
<small>{entry.tags}</small>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{:else}
|
||||
Your queue
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if $loading}
|
||||
LOADING ...
|
||||
{:else}
|
||||
<button on:click={() => entries.load(3)}>LOAD MOAR</button>
|
||||
{/if}
|
||||
|
||||
<header>
|
||||
<a href="https://{$domain}/">{$domain}</a> - {@html $hashtags.map(hashtag => `<a href="https://${$domain}/tags/${hashtag}">#${hashtag}</a>`)}
|
||||
</header>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
|
||||
<script>
|
||||
import { onMount } from 'svelte'
|
||||
import YoutubeViewer from './YoutubeViewer.svelte'
|
||||
import { domain, hashtags, playing, loading, entry as selectedEntry, entries } from './store.js'
|
||||
|
||||
onMount(() => {
|
||||
const unsub = entries.subscribe(async (xs) => {
|
||||
if (xs.length) {
|
||||
const [firstEntry] = xs
|
||||
selectedEntry.set(firstEntry)
|
||||
unsub()
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
entries.load(7)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.app {
|
||||
display: grid;
|
||||
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
grid-template-areas:
|
||||
"header"
|
||||
"player"
|
||||
"queue";
|
||||
}
|
||||
|
||||
.header {
|
||||
grid-area: header;
|
||||
}
|
||||
|
||||
.player {
|
||||
grid-area: player;
|
||||
}
|
||||
|
||||
.queue {
|
||||
grid-area: queue;
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
.app {
|
||||
grid-template-columns: 2fr 3fr;
|
||||
grid-template-rows: auto 1fr;
|
||||
|
||||
grid-template-areas:
|
||||
"header queue"
|
||||
"player queue"
|
||||
}
|
||||
}
|
||||
|
||||
.entry {
|
||||
cursor: pointer;
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
.entry:hover {
|
||||
background-color: plum;
|
||||
}
|
||||
|
||||
.entry.active {
|
||||
background-color: plum;
|
||||
}
|
||||
</style>
|
|
@ -1,45 +0,0 @@
|
|||
<div>
|
||||
<div bind:this={element}></div>
|
||||
|
||||
<button on:click={player.play()}>PLAY UWU</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import YoutubePlayer from 'yt-player'
|
||||
|
||||
export let videoId
|
||||
export let playing
|
||||
|
||||
let element
|
||||
let player
|
||||
|
||||
$: if (player && videoId) {
|
||||
player.load(videoId, true)
|
||||
}
|
||||
|
||||
$: if (player) {
|
||||
if (playing) {
|
||||
player.play()
|
||||
} else {
|
||||
player.pause()
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
player = new YoutubePlayer(element, {
|
||||
autoplay: playing,
|
||||
controls: true, // debug only
|
||||
keyboard: false,
|
||||
fullscreen: false,
|
||||
modestBranding: true,
|
||||
related: false
|
||||
})
|
||||
|
||||
player.on('ended', () => console.log('ended u should select next entry now uwu'))
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,122 @@
|
|||
<main class="app">
|
||||
<header class="header">
|
||||
<h1>Eldritch Radio</h1>
|
||||
</header>
|
||||
|
||||
<section class="viewer">
|
||||
<Viewer></Viewer>
|
||||
</section>
|
||||
|
||||
<section class="queue">
|
||||
<div>
|
||||
{#each $entries as entry}
|
||||
<div class="entry" class:active={entry === $currentEntry} on:click={() => $currentEntry = entry}>
|
||||
<div>{entry.metadata.title}</div>
|
||||
<b>{entry.status.account.username} <small>{entry.status.account.acct}</small></b>
|
||||
<small>{entry.tags}</small>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<button on:click={() => entries.load(5)}>LOAD MOAR</button>
|
||||
</section>
|
||||
|
||||
<section class="controls">
|
||||
<Controls></Controls>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
|
||||
<script>
|
||||
import { onMount } from 'svelte'
|
||||
import Controls from '/components/Controls.svelte'
|
||||
import Viewer from '/components/Viewer.svelte'
|
||||
import { playing, loading, entry as currentEntry, entries } from '/store.js'
|
||||
|
||||
onMount(() => {
|
||||
const unsub = entries.subscribe(async (xs) => {
|
||||
if (xs.length) {
|
||||
const [firstEntry] = xs
|
||||
currentEntry.set(firstEntry)
|
||||
unsub()
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
entries.load(7)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.app {
|
||||
min-width: 100%;
|
||||
min-height: 100%;
|
||||
|
||||
display: grid;
|
||||
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
grid-template-areas:
|
||||
"header"
|
||||
"viewer"
|
||||
"queue"
|
||||
"controls";
|
||||
}
|
||||
|
||||
.header {
|
||||
grid-area: header;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: blueviolet;
|
||||
color: whitesmoke;
|
||||
|
||||
padding: 0.4rem 0.8rem;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.viewer {
|
||||
grid-area: viewer;
|
||||
}
|
||||
|
||||
.queue {
|
||||
grid-area: queue;
|
||||
}
|
||||
|
||||
.controls {
|
||||
grid-area: controls;
|
||||
width: 100%;
|
||||
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
|
||||
background: whitesmoke;
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
.app {
|
||||
grid-template-columns: 2fr 3fr;
|
||||
grid-template-rows: auto 1fr auto;
|
||||
|
||||
grid-template-areas:
|
||||
"header queue"
|
||||
"viewer queue"
|
||||
"controls controls"
|
||||
}
|
||||
}
|
||||
|
||||
.entry {
|
||||
cursor: pointer;
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
.entry:hover {
|
||||
background-color: rgb(219, 184, 219);
|
||||
}
|
||||
|
||||
.entry.active {
|
||||
background-color: plum;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,45 @@
|
|||
<div class="controls">
|
||||
<div class="controls-group">
|
||||
<button on:click={() => $muted = !$muted}>
|
||||
{#if $muted}
|
||||
🔇
|
||||
{:else}
|
||||
{#if $volume < 20}
|
||||
🔈
|
||||
{:else if $volume < 80}
|
||||
🔉
|
||||
{:else}
|
||||
🔊
|
||||
{/if}
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
<input type="range" min="0" max="100" bind:value={$volume}>
|
||||
</div>
|
||||
|
||||
<div class="controls-group">
|
||||
<button on:click={() => entry.previous()}>⏮️</button>
|
||||
<button on:click={() => $playing = !$playing}>{#if $playing}⏸️{:else}▶️{/if}</button>
|
||||
<button on:click={() => entry.next()}>⏭️</button>
|
||||
</div>
|
||||
|
||||
<div class="controls-group">
|
||||
<button>⭐</button>
|
||||
<button>🔁</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
import { playing, volume, muted, entry } from '/store.js'
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.controls {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.controls-group {
|
||||
margin: 0 1rem;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,54 @@
|
|||
<div>
|
||||
<div bind:this={element}></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
import { onMount } from 'svelte'
|
||||
import { get } from 'svelte/store'
|
||||
import YoutubePlayer from 'yt-player'
|
||||
import { entry, playing, volume, muted } from '/store.js'
|
||||
|
||||
let element
|
||||
let player
|
||||
|
||||
$: updateEntry($entry)
|
||||
$: updatePlaying($playing)
|
||||
$: updateVolume($volume)
|
||||
$: updateMuted($muted)
|
||||
|
||||
const updateEntry = (entry) => {
|
||||
if (player && entry) player.load(entry.id, get(playing))
|
||||
}
|
||||
|
||||
const updatePlaying = (playing) => {
|
||||
if (player) playing ? player.play() : player.pause()
|
||||
}
|
||||
|
||||
const updateVolume = (volume) => {
|
||||
if (player) player.setVolume(volume)
|
||||
}
|
||||
|
||||
const updateMuted = (muted) => {
|
||||
if (player) muted ? player.mute() : player.unMute()
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
player = new YoutubePlayer(element, {
|
||||
width: 300,
|
||||
height: 300,
|
||||
autoplay: $playing,
|
||||
controls: true, // debug only
|
||||
keyboard: false,
|
||||
fullscreen: false,
|
||||
modestBranding: true,
|
||||
related: false
|
||||
})
|
||||
|
||||
player.on('ended', () => entry.next())
|
||||
player.on('unplayable', () => entry.next())
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
|
@ -5,6 +5,7 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Eldritch Radio</title>
|
||||
<link rel="stylesheet" href="main.css">
|
||||
</head>
|
||||
<body>
|
||||
<script src="main.js"></script>
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
html, body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import App from './App.svelte'
|
||||
import App from '/components/App.svelte'
|
||||
|
||||
const app = new App({
|
||||
target: document.body
|
||||
|
|
64
src/store.js
64
src/store.js
|
@ -1,5 +1,5 @@
|
|||
import { writable, get } from 'svelte/store'
|
||||
import * as util from './util.js'
|
||||
import * as util from '/util.js'
|
||||
|
||||
export const domain = writable('eldritch.cafe')
|
||||
|
||||
|
@ -11,11 +11,10 @@ export const hashtags = writable([
|
|||
])
|
||||
|
||||
export const playing = writable(true)
|
||||
export const muted = writable(false)
|
||||
export const volume = writable(100)
|
||||
|
||||
export const loading = writable(false)
|
||||
|
||||
export const entries = entriesStore(loading)
|
||||
|
||||
export const entries = entriesStore()
|
||||
export const entry = entryStore(entries)
|
||||
|
||||
|
||||
|
@ -26,8 +25,29 @@ function entryStore(entries) {
|
|||
const store = writable(null)
|
||||
const { set, update, subscribe } = store
|
||||
|
||||
const next = async () => {
|
||||
const entriesList = await get(entries)
|
||||
const select = (entry) => {
|
||||
update(() => entry)
|
||||
|
||||
const entriesList = get(entries)
|
||||
const index = entriesList.indexOf(entry)
|
||||
|
||||
if (index === entriesList.length - 1) {
|
||||
entries.load(1)
|
||||
}
|
||||
}
|
||||
|
||||
const previous = () => {
|
||||
const entriesList = get(entries)
|
||||
|
||||
update(currentEntry => {
|
||||
const index = entriesList.indexOf(currentEntry)
|
||||
|
||||
return index > 0 ? entriesList[index - 1] : null
|
||||
})
|
||||
}
|
||||
|
||||
const next = () => {
|
||||
const entriesList = get(entries)
|
||||
|
||||
update(oldEntry => {
|
||||
if (entriesList.length === 0) {
|
||||
|
@ -51,39 +71,16 @@ function entryStore(entries) {
|
|||
})
|
||||
}
|
||||
|
||||
return { subscribe, set, next }
|
||||
return { subscribe, set: select, previous, next }
|
||||
}
|
||||
|
||||
async function* loader(loading) {
|
||||
loading.set(true)
|
||||
let { statuses, nextLink, previousLink } = await util.fetchTimeline('https://eldritch.cafe/api/v1/timelines/tag/np')
|
||||
loading.set(false)
|
||||
|
||||
yield* util.statusesToEntries(statuses)
|
||||
|
||||
while (true) {
|
||||
loading.set(true)
|
||||
const timeline = await util.fetchTimeline(nextLink)
|
||||
loading.set(false)
|
||||
|
||||
nextLink = timeline.nextLink
|
||||
|
||||
yield* util.statusesToEntries(timeline.statuses)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function entriesStore(loading) {
|
||||
const entriesSteam = loader(loading)
|
||||
function entriesStore() {
|
||||
const entriesSteam = util.statusesToEntries(util.statusesStreaming())
|
||||
|
||||
const store = writable([])
|
||||
const { update, subscribe } = store
|
||||
|
||||
const load = async (number) => {
|
||||
if (get(loading)) {
|
||||
return
|
||||
}
|
||||
|
||||
for (let i = 0; i < number; i++) {
|
||||
const iteratorResult = await entriesSteam.next()
|
||||
|
||||
|
@ -97,4 +94,3 @@ function entriesStore(loading) {
|
|||
|
||||
return { subscribe, load }
|
||||
}
|
||||
|
||||
|
|
64
src/util.js
64
src/util.js
|
@ -1,4 +1,45 @@
|
|||
import getUrls from 'get-urls'
|
||||
import { pipe, asyncFilter, asyncMap, asyncTap, asyncTake } from 'iter-tools'
|
||||
|
||||
export async function* statusesStreaming() {
|
||||
let { statuses, nextLink, previousLink } = await fetchTimeline('https://eldritch.cafe/api/v1/timelines/tag/np')
|
||||
|
||||
yield* statuses
|
||||
|
||||
while (nextLink) {
|
||||
const a = await fetchTimeline(nextLink)
|
||||
|
||||
nextLink = a.nextLink
|
||||
yield* a.statuses
|
||||
}
|
||||
}
|
||||
|
||||
export const statusesToEntries = pipe(
|
||||
asyncMap(status => ({ status, urls: Array.from(getUrls(status.content)).filter(isSupportedUrl) })),
|
||||
asyncFilter(entry => entry.urls.length > 0),
|
||||
asyncMap(async ({ status, urls }) => {
|
||||
const [url] = urls
|
||||
const id = getYoutubeVideoId(url)
|
||||
|
||||
const tags = intersection(status.tags.map(tag => tag.name), [
|
||||
'np',
|
||||
'nowplaying',
|
||||
'tootradio',
|
||||
'pouetradio'
|
||||
])
|
||||
|
||||
const metadata = await fetchYoutubeMetadata(id)
|
||||
|
||||
return { status, url, id, tags, metadata }
|
||||
}),
|
||||
asyncTake(20)
|
||||
)
|
||||
|
||||
function fetchYoutubeMetadata(id) {
|
||||
return fetch(`https://noembed.com/embed?url=https://www.youtube.com/watch?v=${id}`)
|
||||
.then(response => response.json())
|
||||
|
||||
}
|
||||
|
||||
export function isSupportedUrl(urlAsString) {
|
||||
const url = new URL(urlAsString)
|
||||
|
@ -40,27 +81,4 @@ export function parseLinkHeader(link) {
|
|||
}
|
||||
|
||||
return links
|
||||
}
|
||||
|
||||
export function statusesToEntries(statuses) {
|
||||
const entries = []
|
||||
|
||||
return statuses
|
||||
.map(status => {
|
||||
const [url] = Array.from(getUrls(status.content)).filter(isSupportedUrl)
|
||||
|
||||
return { status, url }
|
||||
})
|
||||
.filter(entry => entry.url != null)
|
||||
.map(({ status, url }) => {
|
||||
const id = getYoutubeVideoId(url)
|
||||
const tags = intersection(status.tags.map(tag => tag.name), [
|
||||
'np',
|
||||
'nowplaying',
|
||||
'tootradio',
|
||||
'pouetradio'
|
||||
])
|
||||
|
||||
return { status, url, id, tags }
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue