Compare commits
1274 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 78395e3ea0 | |||
| e269ff3d79 | |||
| be495b1c56 | |||
| 3f032619b4 | |||
| ef5ba706b7 | |||
| 3b91c2224c | |||
| 822d069b82 | |||
| 8af9f2cedf | |||
| 3d8d8eb199 | |||
| a0d599ce2f | |||
| fd7b5ec04e | |||
| c9818c92b3 | |||
| f57163f2bb | |||
| dbdcb4e367 | |||
| e1dd0f9c04 | |||
| 2cb6a88c5e | |||
| eb89ba03db | |||
| cb02c8619d | |||
| 7d296e6a3d | |||
| 35bb5b477b | |||
| 925a9fdea3 | |||
| f0baa6ad67 | |||
| f25749470b | |||
| 8a4985e6a3 | |||
| 2cce60fb9f | |||
| 826acf9a3f | |||
| 186de15781 | |||
| 17dc69f87a | |||
| 8268c7a7af | |||
| a71b99ff10 | |||
| 54c4f49edb | |||
| 92cd1cf58b | |||
| 64570c71ce | |||
| cbc4b7877f | |||
| b62f6feaea | |||
| b6dec385dc | |||
| a12581ed0e | |||
| e651e51c50 | |||
| 56321e52fc | |||
| 73eec1f7ba | |||
| a0cddb256a | |||
| 14b3571af3 | |||
| af08c5094d | |||
| ef12dc8cc5 | |||
| fcfc0361dc | |||
| a8847bdeff | |||
| 3f5ab5ecc6 | |||
| 077c7cae15 | |||
| f916cffd41 | |||
| ea4fb95a3c | |||
| 3dbec85269 | |||
| 2a42c3e715 | |||
| c2a6a3f082 | |||
| 61972a7b2d | |||
| 4a562443b1 | |||
| 9515c1b01a | |||
| 126d891e5e | |||
| dad1df0244 | |||
| aff429e5ea | |||
| 3457c4fcdc | |||
| 74c0358d33 | |||
| 8735d3e81a | |||
| 4180d5b5b1 | |||
| ca751a660f | |||
| fb7f5cf1ba | |||
| fe2f52d76a | |||
| ee32ae93c3 | |||
| 877fc96da1 | |||
| 67a5b4e05a | |||
| d0a5083841 | |||
| 2d8c078a6f | |||
| f543dcff24 | |||
| 5cfa5b0add | |||
| 13feb1e822 | |||
| dc755dbe62 | |||
| b334707463 | |||
| ca093f5b34 | |||
| 2e4226b828 | |||
| 095a3daadd | |||
| baa7c79f6c | |||
| f5e308c5b4 | |||
| c0c29759fa | |||
| ddffbe92a0 | |||
| 888e48eb5b | |||
| 61e7351d0b | |||
| 4b0571e123 | |||
| bf7f707ab7 | |||
| accc320971 | |||
| 4276a9f03a | |||
| 7ae3fb6109 | |||
| 8c96e2fd90 | |||
| 30e5bce38f | |||
| 620028db6d | |||
| 850b5d6b25 | |||
| eaf003f5a7 | |||
| ab1efbe9cf | |||
| 14f8453067 | |||
| 090f112463 | |||
| 01d569f1b5 | |||
| 75670ef3f9 | |||
| f88f703e84 | |||
| a5fdef0196 | |||
| f10eee335d | |||
| 20856891b6 | |||
| 60dd1193ab | |||
| 2bc95af73c | |||
| 178bb65b4a | |||
| f7f046ea53 | |||
| ec0896e17a | |||
| a5f35248bd | |||
| a05eccce9e | |||
| e118777636 | |||
| 7998e8158e | |||
| 14f837d1f0 | |||
| cff1d3cf16 | |||
| b2cd96982e | |||
| a89eb8e857 | |||
| 7c1ccd2cc9 | |||
| e3a12bc601 | |||
| e20508956a | |||
| 4000d0e027 | |||
| 77fcced776 | |||
| e67b5a227f | |||
| 150a0a8a90 | |||
| 269dddc92e | |||
| 508ef5cbbb | |||
| 500d06204c | |||
| fe8436179f | |||
| adedfec1d0 | |||
| b974111eb4 | |||
| 3d7b50a2e3 | |||
| 32c11d1f61 | |||
| 5c48747d0f | |||
| 5a0abb3595 | |||
| 95b98c9053 | |||
| 518e09e3cb | |||
| ad632b0680 | |||
| 73aa925067 | |||
| 391b0ac7ab | |||
| ae2bb7afcd | |||
| 0681c3ae08 | |||
| 4aa96bb288 | |||
| b24739d70d | |||
| dffe8e055a | |||
| adadc8bb3e | |||
| 5f3562fe5b | |||
| 0d7c535a70 | |||
| 39f39f23a7 | |||
| ff0de6f2e7 | |||
| 8658b3c0d3 | |||
| 8551bc2dc1 | |||
| a0abf336cf | |||
| 7d71dd0507 | |||
| 54c190f86c | |||
| e198c69741 | |||
| 7c1d3e52e5 | |||
| 5460719297 | |||
| 83b770e4e8 | |||
| a5c9835d9d | |||
| f0840b78e1 | |||
| ab79f1e0e6 | |||
| 16a2f511bb | |||
| 39fdd72e9f | |||
| f93a7e284c | |||
| b5e480a486 | |||
| bf128babcc | |||
| 46d1ad221f | |||
| 7aacbec108 | |||
| f77506c454 | |||
| e3f1f21ce9 | |||
| 08c86615a0 | |||
| b0425b37c0 | |||
| d94be7aa5e | |||
| 40bdddd031 | |||
| da94dee780 | |||
| 587a21c5f6 | |||
| f994617d44 | |||
| a92f5fc2a1 | |||
| 208a360c47 | |||
| f728cbf817 | |||
| aef5665cb5 | |||
| 6ea47d26d0 | |||
| a7a36033a2 | |||
| 32f4d99c98 | |||
| 2d08a2f339 | |||
| 1f0ebff241 | |||
| 5c26e49c17 | |||
| ea85f2f5d6 | |||
| af9b123e8d | |||
| d322c0909a | |||
| 0bfe84d73d | |||
| 244e8227e7 | |||
| 5da8073235 | |||
| 5479b7ee95 | |||
| b4e932581c | |||
| 1f65cca002 | |||
| 1a9cdfa93b | |||
| d780a8cb12 | |||
| eaf619f4f8 | |||
| 3dd8584954 | |||
| ebdc26a00d | |||
| dd47dd09da | |||
| bc6d2ac549 | |||
| b2cd8197d4 | |||
| 23ea9e77c4 | |||
| 42ac51f1e9 | |||
| d433878680 | |||
| 11fa12001c | |||
| a82a6963e9 | |||
| a98a80001c | |||
| ad1e73d0cc | |||
| 55c0e7e8c5 | |||
| 3d35ae32c8 | |||
| 33e3bec337 | |||
| 9c12272846 | |||
| 0cc2249b97 | |||
| a5d0c0b6b7 | |||
| 3736a55bad | |||
| 303213da73 | |||
| 8d47eaf74c | |||
| 9f10c0dcf4 | |||
| d981e4c2c6 | |||
| 8f3d1bd091 | |||
| 3b9f557075 | |||
| 18cdec51d9 | |||
| 276a51b8c3 | |||
| 14e1d6aad3 | |||
| 9c65300fc4 | |||
| cf01dd9397 | |||
| 162318b439 | |||
| cd89cca984 | |||
| 25222c7310 | |||
| b2bfde4426 | |||
| 8602b53260 | |||
| 145b361581 | |||
| 7743dd8e7f | |||
| ed2886d449 | |||
| 002dfacc3e | |||
| fd9fbc32a8 | |||
| e54deeb4f7 | |||
| c85f211d5e | |||
| 67f848a9ee | |||
| fe336122e0 | |||
| 6d8d8493a6 | |||
| 5d7d249fff | |||
| 35ca030dfb | |||
| fab6fb0f5b | |||
| 3b8fc807f7 | |||
| 13cfb9ff14 | |||
| aabd5e75d8 | |||
| 7be6fb9501 | |||
| 9fc63e5cd3 | |||
| 7a1e8be48a | |||
| d299a65e03 | |||
| 2337f7a3ba | |||
| 4853d7e2fd | |||
| ca1a375f2d | |||
| 0d098815a2 | |||
| 30cbfb6ffd | |||
| 4c7deffdfe | |||
| 15133e561f | |||
| c235b52e3b | |||
| 903f356252 | |||
| 54c44b4379 | |||
| dcbeecc8cf | |||
| bf182c65ad | |||
| 7c5e4101b0 | |||
| 2736ac418f | |||
| e2dac10501 | |||
| c15d7ab28e | |||
| de78f8b1d5 | |||
| e56d32180c | |||
| 6c92837317 | |||
| d4015a86ef | |||
| 4b4be96e26 | |||
| 823e1d983c | |||
| 47fba5b81a | |||
| ee23ebb705 | |||
| d6d3a95daf | |||
| e839e632fc | |||
| cb6f0a9a32 | |||
| c5515934d9 | |||
| 7dafe85c53 | |||
| 3bc65c252d | |||
| d4f9ab9f37 | |||
| b96227b07a | |||
| da12ee452c | |||
| 99df043b5f | |||
| 54d53f1b91 | |||
| 3645b9ffcf | |||
| 9d0ce25be7 | |||
| 1c860f74fb | |||
| 75e31dc3a4 | |||
| 10a8452709 | |||
| f824f73491 | |||
| 16c34e3d52 | |||
| 76c4599dee | |||
| 1af80096c7 | |||
| cbb2bb4502 | |||
| 0f3bcaa8e6 | |||
| ae6575043e | |||
| 0dbd5b456c | |||
| 81f3e62446 | |||
| e0bb25466d | |||
| cb334aab0d | |||
| 9cef850d06 | |||
| 79ecc87db4 | |||
| 12644bfbea | |||
| bc6b58aa3f | |||
| abeded1d03 | |||
| a466f77b6e | |||
| 968ce5432a | |||
| 277506b3c8 | |||
| 1c48ea1908 | |||
| f2ac1557a4 | |||
| 7213c1e35f | |||
| 9f5f953b46 | |||
| 3ff6054c20 | |||
| 9fb25254a6 | |||
| 8c6a06d490 | |||
| dd37837848 | |||
| 5ca9325e16 | |||
| b8fdf0911a | |||
| a7302fee4b | |||
| 41bf4d2764 | |||
| 0970140a3a | |||
| b8eb8ec052 | |||
| 46b7d6a25a | |||
| aab2f5b014 | |||
| 6062094e6f | |||
| f7e36619df | |||
| 167f5173cd | |||
| c7ebd42fe9 | |||
| 94018c6ee3 | |||
| 388327ec1d | |||
| 0ba56c271d | |||
| bd1216c275 | |||
| 3180063fe3 | |||
| 9cb9a6607b | |||
| 42cdcd5c25 | |||
| 8b1da27af7 | |||
| 638e228ce9 | |||
| 94469ccd0e | |||
| 48f7e1f39c | |||
| 60f7abfa42 | |||
| cd391db302 | |||
| 8613d6971f | |||
| cc18153889 | |||
| be4bf9e2a3 | |||
| 085bd4aab9 | |||
| 977d103c3a | |||
| 0778f07922 | |||
| e12154601b | |||
| b22d50f760 | |||
| 302f21b477 | |||
| 65da515854 | |||
| e9bd3154a5 | |||
| 1da147e110 | |||
| e7dd85420c | |||
| 3e542871c6 | |||
| 26b78f1225 | |||
| 57261b5094 | |||
| c7f24133e5 | |||
| 87aa397a7c | |||
| e2a18ec765 | |||
| 26ee2ffbb6 | |||
| 38dd1ddbe8 | |||
| 11bc79520a | |||
| cc0ae60c83 | |||
| 30b028d836 | |||
| a30a70c6b5 | |||
| 9503003a4a | |||
| a58edad8ea | |||
| d0ba619b45 | |||
| 64bad6758b | |||
| b902b8f9ba | |||
| 7bb4ee96e7 | |||
| 6c3e960096 | |||
| cacb0f1ea0 | |||
| 5a2e33ccd2 | |||
| 5d389a0796 | |||
| 6db1d8af2a | |||
| 51d73e09ae | |||
| 3c324aa1b2 | |||
| a94092ac78 | |||
| f9d72d12b6 | |||
| 81e11c2ae9 | |||
| f7a82ccdf7 | |||
| 44e3395835 | |||
| d558717152 | |||
| b9b021f466 | |||
| 13bfd63dc2 | |||
| 9600c4f1c9 | |||
| de9696471a | |||
| c954f8b984 | |||
| 08479242f7 | |||
| d70783cc82 | |||
| a2f0617d5a | |||
| 27b6ca824d | |||
| e84f8ee541 | |||
| ad87dbdcb9 | |||
| 9c42c120f5 | |||
| 740f2e8f6f | |||
| a0388689cf | |||
| 46cb5e1078 | |||
| abc53cf00f | |||
| 68f367bd39 | |||
| d54097799c | |||
| ffc25520bb | |||
| ccdc68870a | |||
| 7820da181e | |||
| ec4d43d2ec | |||
| c12a04b55f | |||
| e0fe4c7103 | |||
| 82f2983ac2 | |||
| d2733cf486 | |||
| 3aabd298ec | |||
| a0e375d2dc | |||
| 8806c8ea0c | |||
| f0120010fe | |||
| a5a121ee00 | |||
| 459a0eaa91 | |||
| 84fefbaa14 | |||
| 023b8c370a | |||
| 0a023ac6f2 | |||
| 83f7e4498a | |||
| d57040fc5b | |||
| 7df62ba530 | |||
| f51ec50113 | |||
| 6748cbe52d | |||
| fda2dcc484 | |||
| 07a5f06695 | |||
| c9392911d9 | |||
| 88dec55116 | |||
| f30de3f4a0 | |||
| 3c5aafee2d | |||
| 52b4e48217 | |||
| 58ed3b1ca4 | |||
| bfef49bc33 | |||
| b7a61e5bc0 | |||
|
|
15a3ffd406 | ||
| 66f934e46e | |||
| 47b85cfebc | |||
| d0b060f31a | |||
| a756a9dc16 | |||
| 207b82cc8b | |||
| 26df9633b4 | |||
| 2b749d6a3b | |||
| 8c403f106c | |||
| 36aa226e9c | |||
|
|
1946b77285 | ||
| a09ea26b0e | |||
| 1556506bd7 | |||
| f94f6ec956 | |||
| aae566b82a | |||
| dd8aeefdfd | |||
| 7d951a5115 | |||
| 556722693c | |||
| 78f350b72b | |||
| 340ee05443 | |||
| 561a1aa5a5 | |||
| e2fa4f9cfc | |||
| ee56187c9d | |||
| d3dbceb749 | |||
| 799c953b30 | |||
| e766d6da69 | |||
| 53ed81fa81 | |||
| 320d35d9e5 | |||
| a1bfaaaf92 | |||
| 03ebc87aa5 | |||
| d2447d2d8a | |||
| 296f146835 | |||
| 9a7e38d897 | |||
| f0fcf996c9 | |||
| c1b14979a2 | |||
| 9e9fdfe37f | |||
| 135ce2dfdd | |||
| 167d46ca76 | |||
| 23d2c252fe | |||
| 9100c1d9de | |||
| b45b232ecf | |||
| 9648d30aa2 | |||
| 5a04c2eeee | |||
| 74fe85f3a2 | |||
| 4b44efe6fc | |||
|
|
8922054c44 | ||
| 302353f448 | |||
| 3891d946e0 | |||
| e92e2e22f5 | |||
| 4f4eb46a60 | |||
| 0daef615c1 | |||
| 6ce8bba6b7 | |||
| 09cf3ce54e | |||
| 872b0dea6e | |||
| 376f20aeab | |||
| 57aae96781 | |||
| 7c27c33aa0 | |||
| 7dc354509b | |||
| 7c09440084 | |||
|
|
5538ec4d3d | ||
| 89168b643c | |||
| 7fefe255ea | |||
| 3423aac49a | |||
|
|
cc8d1d8a94 | ||
|
|
c0a863740a | ||
|
|
f006eed113 | ||
| 3f131f44d1 | |||
| 5cfc089bf1 | |||
| 987b1ad655 | |||
| 1cd06040da | |||
| f99220bf8b | |||
| 8bef1d0c99 | |||
| 47a73cfa6c | |||
| 2dd901b1d5 | |||
| 46725c6fa4 | |||
|
|
adfc3fca5f | ||
|
|
077610820c | ||
|
|
9a3bd3ff88 | ||
| 85fed9d4c5 | |||
| 5a2d8d22cb | |||
| 761f2c21a8 | |||
| c34340d0d1 | |||
| 045cd5a528 | |||
| 27b0609d34 | |||
| 65556d9cad | |||
| 30835e94c0 | |||
| ee8ac1d9ee | |||
| 2be44d54ae | |||
| cf8bedfad9 | |||
| 5dc9b498d4 | |||
| fb7a168d31 | |||
| 484db037c0 | |||
| 15a89583a2 | |||
| 75cc0d5a87 | |||
| 0e3b962a00 | |||
| afaf65a53d | |||
| f00d3b5aaf | |||
| 03bd442783 | |||
| 787bd1a532 | |||
| bd5befc18c | |||
| 829047585a | |||
| 78da45d468 | |||
| 7f7ab7b752 | |||
| 79fb10c556 | |||
| ffd9c6fb7a | |||
| 4074774d3a | |||
| e7d43c566f | |||
| 52b94003bf | |||
| 13bd280062 | |||
| 209e4221d2 | |||
| c4832a9ef7 | |||
| 6cb8cc174b | |||
| e9b882376c | |||
| a4948d3eef | |||
| 746a78577e | |||
| 268ef7dae3 | |||
| 6c1d666fdf | |||
| 53fb3e8f46 | |||
| 2df0b1a0fb | |||
| 3846d1a926 | |||
| 2a747e11af | |||
| cda6b4e10d | |||
| 502ce707e1 | |||
| 5b1f73a69e | |||
| f7eba0974f | |||
| 2ae84bccd8 | |||
| 3ac3f1aee5 | |||
| 23ca74afdb | |||
| 7d894fb1e3 | |||
| b39e6be952 | |||
| 415ebf6821 | |||
| b1ec4f7387 | |||
| 820af9b133 | |||
| 54c88dcb2f | |||
| 104ed85d72 | |||
| 84de5927cd | |||
| 1b829e095b | |||
| cbb4624e25 | |||
| 2cb02ff3f2 | |||
| b8cd441109 | |||
| 86a8c23999 | |||
| a2573b5aaf | |||
| ea8671f64b | |||
| a234903b8f | |||
| 981da95cde | |||
| 18fe17f00f | |||
| 5ad8302084 | |||
| cfcd0c5945 | |||
| eb1b1cf73d | |||
| 95d3d962f4 | |||
| 6eb96f917d | |||
| 5847215097 | |||
| d102a8cd7d | |||
| 571ac92cd3 | |||
| 246743203f | |||
| 1cc76ab3b1 | |||
| 5c9112a07b | |||
| d92b24a2f6 | |||
| 7729bf545e | |||
| c14aa99e5b | |||
| 2ced591ed4 | |||
| 56dcd0feb3 | |||
| c43b1daa42 | |||
| 5e0f7c6160 | |||
| cbaa424fc4 | |||
| c2039da7c8 | |||
| 6d4b161b06 | |||
| fef2c26cbe | |||
| 50719113b1 | |||
| 4e407e2cf8 | |||
| 336879a4a1 | |||
| 7fd28616aa | |||
| fba6a8d604 | |||
| e5c1bcded1 | |||
| b99929063c | |||
| 18d7414b90 | |||
|
|
4427844a41 | ||
|
|
e92f53f590 | ||
| 965a48c086 | |||
| 0edd82479e | |||
| 338b219142 | |||
| c504d5594c | |||
| 9beeffc679 | |||
| 19e021ce8e | |||
| c863447f3d | |||
| 520b0b7ade | |||
| 48e12c8504 | |||
| 60f940f4db | |||
| 0bed840b6f | |||
| ddbddc45a9 | |||
| a039fd4ce4 | |||
| d3c3c99069 | |||
| 843a405323 | |||
| cb548c3a3d | |||
| d406438527 | |||
| 6737061713 | |||
| 46c635b11b | |||
| 5f04c03f80 | |||
| dbf96f97b6 | |||
| 861e62dc56 | |||
| 1b00420388 | |||
| 0ac57ca2ac | |||
| 58e29f4590 | |||
| 8e544a6ae0 | |||
| 2cf42cb6ed | |||
| 93eb67ca49 | |||
| 58302ea87c | |||
| f17bae8f39 | |||
| fee73519e6 | |||
| 5d1ad6f935 | |||
| 6d07fc24e4 | |||
| a720556ac9 | |||
| 0bc1254ce0 | |||
| f8bd8e2eec | |||
| d0beb137fe | |||
| c6e4d06fa1 | |||
| 190737b915 | |||
| 902f880a68 | |||
| 624c072bb3 | |||
| 09253fe0a1 | |||
| a0eeabbd50 | |||
| 873a8cf3a8 | |||
| 9c1fb77dc8 | |||
| 0a3627c7fe | |||
| 2a4a3f7192 | |||
| 0b30688d59 | |||
| 5cbfb70306 | |||
| 63e40cf71a | |||
| d1baebcceb | |||
| c07916362e | |||
| d382b46464 | |||
| 78c66a1543 | |||
| 48319acc31 | |||
| b634e4d2b3 | |||
| 03f37fa1a3 | |||
| 3185e500d1 | |||
| bf010a243c | |||
| 9d2b480f3f | |||
| b69a03d42f | |||
| f1330570d6 | |||
| 589a1fefbf | |||
| d64b41a363 | |||
| e7613aa691 | |||
| e272d1d4d4 | |||
| 0dc196127e | |||
| 42394a48bc | |||
| 6546ad2166 | |||
| cd7a1de548 | |||
| d644395b35 | |||
| 5b0fcef15c | |||
|
|
bd1a904fd5 | ||
| a9ab8d8b92 | |||
| bb3fcc646e | |||
| 0f5111e1e2 | |||
| 73eb5ffe93 | |||
| 8cefa4af2f | |||
| ede570865f | |||
| 038191d38e | |||
| 6a1f07d881 | |||
| 6c59c009e5 | |||
| 4354393f79 | |||
| d215a10486 | |||
| a699166368 | |||
| b83be3de7d | |||
| db287adaed | |||
| 7714adb843 | |||
| 3eca178df5 | |||
| 87310f95cc | |||
| 57ad81a7a4 | |||
| c1e0e7b280 | |||
| 006c9ef975 | |||
| 08fceafc17 | |||
| 357d4829eb | |||
| e5d1addc00 | |||
| d4868020f4 | |||
| 7e41768aba | |||
| 075168ed85 | |||
| 8475340999 | |||
| a3bd8a8b7e | |||
| 7e49b493bb | |||
| 888ffcaded | |||
| 83b8ce0aaa | |||
| 7f77847baa | |||
| 72da7eb5f4 | |||
| 5c6ea49c66 | |||
| 6de5ae6355 | |||
| ec2cd3d6a0 | |||
| 6031e35133 | |||
| b322ef0db1 | |||
| 4febd2f602 | |||
| 46360d1b1f | |||
| 2d8bf0c0ee | |||
| a907d0d404 | |||
| 0117d44422 | |||
| 4fdd6af291 | |||
| 9a864bc11c | |||
| ee3ccef0e4 | |||
| d9053e26b5 | |||
| efefa4ef32 | |||
| cfc7f6aca6 | |||
| 3b79829b72 | |||
| 4d1da636e3 | |||
| 226cfb654e | |||
| dc71a25863 | |||
| cb5218e52d | |||
| f63fd895fe | |||
| 5efa8c88c1 | |||
| 870384f83b | |||
| 02b4b44780 | |||
| ea186d0aeb | |||
| 96a5b8da8f | |||
| f961c8aea8 | |||
| f3a9c06f92 | |||
| 78c04523c0 | |||
| fff575f762 | |||
| bc19164c25 | |||
| 3ab619799a | |||
| f1613b950c | |||
| ee9599d040 | |||
| 851f0e0e84 | |||
| 56ebd2d36c | |||
| 38d1be5fc2 | |||
| 35208cf9e8 | |||
| 0aa7beb525 | |||
| 71cb357424 | |||
| bc5d5046df | |||
| 5bd2952437 | |||
| 9a0c0ba810 | |||
| a5bf3ec1e0 | |||
| 84da78e2e8 | |||
| 13b89eb2a9 | |||
| 89c524a690 | |||
| d44d1b6e64 | |||
| 8ea5b3d5be | |||
| f8c774700e | |||
| ef410d9e01 | |||
| 328eba1d6c | |||
| b819fc816b | |||
| 1410f699c5 | |||
| be6df178f6 | |||
| 2a7001ea3a | |||
| e2eaa206e4 | |||
| 437890a609 | |||
| 2025547d33 | |||
| 884db1c5e3 | |||
| aa1ccedad5 | |||
| df2b0f5614 | |||
| aec87e7681 | |||
| bf26961aaa | |||
| 67ad0c54b4 | |||
| fda7b38fa1 | |||
| 5b5f4204bb | |||
| 167b9d4305 | |||
| 0760e447db | |||
| 897e65567b | |||
| 7e403e3406 | |||
| 7cb378029c | |||
| f4ff7e7ed6 | |||
| 4c359675b6 | |||
| 5987fda0b2 | |||
| 066b74e1ac | |||
| af7833a372 | |||
| f554030f3c | |||
| 0e0eb57fd9 | |||
| 485a5c13f0 | |||
| bb8d63f67d | |||
| 6340541daf | |||
| 028aceef2c | |||
| 5e96cdcad8 | |||
| c04780bb16 | |||
| 84d33a9ccb | |||
| b30369dfea | |||
| a5ef24a50f | |||
| 9b1067afc4 | |||
| 433057bb19 | |||
| 3704ea7962 | |||
| 76b380d6c3 | |||
| 804509b3c5 | |||
| 1e8fd6aad4 | |||
| fbcf4c0c19 | |||
| 0234ea7871 | |||
| 75ec1969c0 | |||
| bcc3d89342 | |||
| e4194e029c | |||
| d673ed089f | |||
| 96ddc8a84b | |||
| 70f7b88d71 | |||
| f174ddab68 | |||
| f7c373481d | |||
| 33d16221cc | |||
| dd4d71a496 | |||
| c2f1c6980f | |||
| c0c454f3ef | |||
| 4b5c5a8dce | |||
| becf7c328e | |||
| fbe58ec54d | |||
| 609247cbf0 | |||
| a9f99d93be | |||
| be82c4dae6 | |||
| 94a9b1e56f | |||
| 769ff6aace | |||
| 6e1fe930ac | |||
| 93b8049b01 | |||
| 5cf74b4405 | |||
| 2aa91df820 | |||
| 85fedbfeaf | |||
| 1cbe74252c | |||
| 9fb9667dba | |||
| 55b6ca7392 | |||
| 758c2bf0bc | |||
| 6eed271814 | |||
| 7a5a4bddf8 | |||
| 3f8ea683ef | |||
| 29dff35bb4 | |||
| c1c9dd1625 | |||
| ffa38cf4ff | |||
| 3d64a08b08 | |||
| 79462c320d | |||
| 7881ae5290 | |||
| 2d6052d715 | |||
| b4e8622af3 | |||
| 7c2cd5cf03 | |||
| 2a350f404a | |||
| 6907f65439 | |||
| 5713037c4d | |||
| cd26a1ceed | |||
| c05eed44f0 | |||
| f27b3f1895 | |||
| f4f69d4eaa | |||
| 1badce511e | |||
|
|
595109024a | ||
| f55731e793 | |||
| 6061a3efc5 | |||
| b6cae36528 | |||
| 7d8f3912bb | |||
| eb02865131 | |||
| 0310e1e2de | |||
| 12b15c58f8 | |||
| 4635dc4eec | |||
| 0a5243837f | |||
| 37eccb12f2 | |||
| 84db9361f9 | |||
| 3fd432f432 | |||
| d4947571a4 | |||
| e8e9db2bbc | |||
| 4ed998a9ab | |||
| 7900bd18db | |||
| 357b7a0365 | |||
| 145b237701 | |||
| 38ef7905c7 | |||
| 38641df28e | |||
| 55fbd30699 | |||
| 5e4aca66c5 | |||
| 8325e44a0e | |||
| a91ed7368f | |||
| cda4b81554 | |||
| be759c512a | |||
| cfde1ee58c | |||
| db866bf8d8 | |||
| d32c083406 | |||
| e56551c79d | |||
| 97099ece1c | |||
| f05f37841a | |||
| dbc0c8c741 | |||
| 2ab01c1013 | |||
| ada7e69abd | |||
| 27e03115d2 | |||
| 1b6d20fa0b | |||
| aa186da1bd | |||
| a6a09a10b5 | |||
| e25a0a954c | |||
| 09f203dd64 | |||
| 1858e092a9 | |||
| e5f2bde6bc | |||
| 0848b2fe67 | |||
| 691675bdc2 | |||
| 1e70ea51cf | |||
| f8e3a39620 | |||
| 6b91444f3c | |||
| 4f7fc75d6b | |||
| 51735ad8c2 | |||
| 78c34c77e0 | |||
| 65dacee829 | |||
| 288f2334f6 | |||
| 43a86a7495 | |||
| 28c12861e5 | |||
| 7fb9aad0d0 | |||
| 48167623ff | |||
| 43b136ca46 | |||
| ec83830e78 | |||
| 7093236237 | |||
| c7d133ba3d | |||
| 47475ac597 | |||
| 4f478a4667 | |||
| b55d9dd7b9 | |||
| 02fa4e2fe8 | |||
| e5206b524f | |||
|
|
5695534a9a | ||
| ca6083b0ee | |||
| 8d29e7b5bb | |||
| 7f9766ff95 | |||
| 5098c45174 | |||
| 6ca6fcd10c | |||
| 86a184fe29 | |||
| 19f2ef7d68 | |||
|
|
74ea6fa238 | ||
| 87a65d4af2 | |||
| 2eec8b3ef9 | |||
| cfa0c4e2c8 | |||
| ced35084ba | |||
| 20346a784e | |||
| 4f6f946245 | |||
| 16a2a424d2 | |||
| 2aea741372 | |||
| 53073ecdfa | |||
| 8216965533 | |||
| 88ce945a47 | |||
| fef30ce3e9 | |||
| 0cdbb19b7a | |||
| 69e44c4964 | |||
| d59a78b039 | |||
| e7957b6503 | |||
| 3b4b52f6a1 | |||
| a4bca8af45 | |||
| cc53df5999 | |||
| a271b4d63e | |||
| 5c73f97c54 | |||
| ecc65676f2 | |||
| 619d89c4f5 | |||
| fe66c9dfbc | |||
| 95d8d8a8cb | |||
| ee7d958224 | |||
| 88a3298cfa | |||
| 4a761efaed | |||
| fc8d011758 | |||
| 3708b99186 | |||
| 291c4fdd4d | |||
| 7d8758d1ee | |||
| 9019963597 | |||
| 7bb3d079f7 | |||
| 84470846ef | |||
| c17e6f0f6d | |||
| a0031be90a | |||
| ed0e51114c | |||
| 1646958449 | |||
| e1b062f19e | |||
| ff0dc23d5d | |||
| cb91e2a09f | |||
| 103ecf7a83 | |||
| ccde079aee | |||
| 10f1bae7de | |||
| dfdc26fe2b | |||
| 06cc9220c2 | |||
| a7f654650d | |||
| ff525f159b | |||
| 44eef78d29 | |||
| 7c80da3b5a | |||
|
|
d2a6dfe55d | ||
|
|
7873777b96 | ||
| 1268c2b0cc | |||
| d6e6c4ad40 | |||
| 0272597bda | |||
| 2e7b7f6c30 | |||
| e447b39beb | |||
| 9df8bdf3b9 | |||
| 95676d5343 | |||
| 309f738d25 | |||
| d2bd215877 | |||
| c2ebe34ab6 | |||
| c83f8530f8 | |||
| e9795f4a00 | |||
|
|
d63197b13a | ||
| 72a3bba8ad | |||
| f0cc710621 | |||
| 49ca4b64e3 | |||
| 06a2f67759 | |||
| 783684570c | |||
| 5998b6c0b2 | |||
| 6a3cc8e03e | |||
| a629bbeab7 | |||
| 7e74aa2c3b | |||
| 9a5ccf1602 | |||
| d7d0f69a58 | |||
| 217206ca1e | |||
| a7fa66b840 | |||
| 4df771aae1 | |||
| 77f3ce0e39 | |||
| b353e6f34d | |||
| e9dfd834f7 | |||
| c9b354e06a | |||
| 7b845553e2 | |||
| fe72a4fdec | |||
| 6e6b3522fe | |||
| bf0d3b99cf | |||
| 7d4e4fc520 | |||
| e4480a91ef | |||
| e2d393d500 | |||
| ce6a22d31b | |||
| 85290ae832 | |||
| 3f51048c1a | |||
| a81c5765e1 | |||
| cf1cc3d87a | |||
| 2a1419eb53 | |||
| 0edb5d5c4f | |||
| 5b417d3f47 | |||
| eee7d622b8 | |||
| bbc4247893 | |||
| 26757c0b2d | |||
| b3fce146f5 | |||
| 80c14f72be | |||
| 6ea2b0488d | |||
| 5cc90ea368 | |||
| d8181431ff | |||
| e681a64f55 | |||
| 92c4ba0c69 | |||
| c26744b9d9 | |||
| 4c41601319 | |||
| 210290e7f4 | |||
| 95aec0f68c | |||
| a600f30ff8 | |||
| ec046767c7 | |||
| f86ef5bda6 | |||
| 77ee6be218 | |||
| 1c978e61b7 | |||
| aba099d577 | |||
| 33e05014fd | |||
| c5f6868a63 | |||
| 4b177a9724 | |||
| 9a2261faed | |||
| d009b53dba | |||
| acfa833203 | |||
| 4c0aca48fd | |||
| 66169ddeb1 | |||
| 72b28918e9 | |||
| 7eca642da3 | |||
| 4fa1cb5cbc | |||
| 18a3a2162a | |||
| 6054da346d | |||
| ef52eec860 | |||
| c88247df25 | |||
| f028af8b98 | |||
| 51526b5dcc | |||
| 2ead2f8e41 | |||
| c4914b4e5b | |||
| a5685fa420 | |||
| f53faac344 | |||
| c9f227c0f9 | |||
| 470f576b41 | |||
| 8945226d24 | |||
| 6b7f41ecf8 | |||
| dd74725c0a | |||
| 9338711d4c | |||
| ddf8c3210f | |||
| 806a889726 | |||
| 0ab21f8360 | |||
| 7f806a55af | |||
| 31ed290390 | |||
| 2788b9e439 | |||
| 355108e0ef | |||
| 0eb3840221 | |||
| 6013fd68ff | |||
| 63922d5744 | |||
| f721c795e6 | |||
| 006e684f17 | |||
| e529239c8d | |||
| 21e24cc226 | |||
| 435f2890c4 | |||
| 179614bd85 | |||
| f0f02032cc | |||
| 8a70fc5f81 | |||
| ef0a802c16 | |||
| 22bcd02afc | |||
| bd2fa787e1 | |||
| 87e65a99d4 | |||
| 0cd644a38a | |||
| 78364472ad | |||
| 8eed3431c8 | |||
| f5bde78378 | |||
| 390b788b61 | |||
| 7cb77a7a58 | |||
| a1395da957 | |||
| 983c7747cc | |||
| b1538e9a8c | |||
| 646d98588a | |||
| 5890192751 | |||
| e6b1370f71 | |||
| 6aba6e0277 | |||
| 89a459e42b | |||
| 2ad5a7b205 | |||
| d2fdbe178c | |||
| 1cdf246919 | |||
| 810fbbdc97 | |||
| 0abd4fc776 | |||
| 97d97e022b | |||
| 78ff7d7e40 | |||
| 1899f486e1 | |||
| 9ba1afebee | |||
| c8d943da0b | |||
| f5f3e2754f | |||
| 0cbd62352e | |||
| 3e565ddbfc | |||
| c84900def0 | |||
| 1588ebd274 | |||
| 691f45fbcc | |||
| c62033fb28 | |||
| dfabfaad48 | |||
| f770b0dbf1 | |||
| f3e5aeb9e6 | |||
| f1d2881359 | |||
| 8c78715271 | |||
| 8b648fb4d7 | |||
| 46fc868109 | |||
| d633cb3b97 | |||
| 5372acdf07 | |||
| 9234b86e46 | |||
| 6031a99af7 | |||
| 6f5768567f | |||
| 65b9822e24 | |||
| d78a83c4e3 | |||
| fe85f6a7a5 | |||
| 6b78e5652e | |||
| bbef2aa2e7 | |||
| 13f4dcb215 | |||
| ff82807130 | |||
| c7980ab892 | |||
| 753a9348f4 | |||
| 79e781dcd8 | |||
| 6b9bf1b70f | |||
| 76b1da1b1c | |||
| 7fdb15821a | |||
| d2c1b2de3e | |||
| d6201dc2ac | |||
| 0f6b707d69 | |||
| 90c1ba0b6c | |||
| 73872d4188 | |||
| 936cb78b26 | |||
| 2dca756b20 | |||
| 3d2bbe742b | |||
| bc51505727 | |||
| c8418f0373 | |||
| 39a7a8187e | |||
| d66cfd430d | |||
| d9430dc90d | |||
| 1c1d31e563 | |||
| bf8d133962 | |||
| 0920c234d0 | |||
| 4cd0eac01b | |||
| c08ffa9c58 | |||
| 410cd4d7f3 | |||
| 3d7a47662e | |||
| e37ad4ba7a | |||
| 377a58c427 | |||
| 0c6355e370 | |||
| b9e8d111e6 | |||
| 73b1a57749 | |||
| 0801c122e2 | |||
| e58f5c4b62 | |||
| 4bfb116d45 | |||
| 96d7456ea1 | |||
| 18cf4e5d0f | |||
| 6283bd74e5 | |||
| 585ad1f525 | |||
| 33d9bbe00b | |||
| 8b066c3692 | |||
| 291699b925 | |||
| 14a2233ff2 | |||
| cc6d08e452 | |||
| 1cd2af7c58 | |||
| a1b89e0fc2 | |||
| 9036bb5810 | |||
| 5d200805f0 | |||
| 30d6338d76 | |||
| bd38e28610 | |||
| 967a5bf87b | |||
| 2675960aa0 | |||
| 289baf73e5 | |||
| 5a6195d36d | |||
| d359b60bc5 | |||
| 1581e3382c | |||
| 6355e31004 | |||
| ec36ca1bc8 | |||
| 51d0cbf16f | |||
| db5d69d92f | |||
| 470f5481ee | |||
| e51a2f4193 | |||
| 62b986575c | |||
| 4be2d902b8 | |||
| 709cb7763c | |||
| f469c29cc8 | |||
| cc36305568 | |||
| b43747a59c | |||
| fcd9deac6f | |||
| 97ef4c2fec | |||
| 0eadb1aac6 | |||
| 5fc867b4a3 | |||
| 414bf7b36f | |||
| 4fc68db7f8 | |||
| 08460a923d | |||
| 5f4abcec1f | |||
| 1157a8a8e2 | |||
| b2f6a1fddd | |||
| b6ff740b84 | |||
| 0e09ae771c | |||
| 970b9b6b1d | |||
| 83a4dd08ac | |||
| f806d154ee | |||
| b128f97a7c | |||
| dbc35ccbd1 | |||
| 5becf0bdf0 | |||
| 04c33956a1 | |||
| 42d85f75f7 | |||
| 18f0c7540b | |||
| 1c4c4d5652 | |||
| ab42557283 | |||
| 5e20925ec5 | |||
| 9b8e500927 | |||
| 83d84e5a93 | |||
| 470a7e8883 | |||
| 2422cec1e9 | |||
| bde7eb6327 | |||
| 6af771e1ff | |||
| 828ded6b29 | |||
| f05a0813d2 | |||
| 854bc05d14 | |||
| cda5d87735 | |||
| d0058cedb9 | |||
| d5a6e8d707 | |||
| 8fde5bb3d6 | |||
| ab06f0bd41 | |||
| f652bce8fe | |||
| 261f9a6b8d | |||
| cd4e3768e2 | |||
| 4a9ef1f9db | |||
| 3768575bad |
5
.babelrc
5
.babelrc
@ -1,12 +1,13 @@
|
|||||||
{
|
{
|
||||||
"plugins": [
|
"plugins": [
|
||||||
|
"@babel/plugin-proposal-class-properties"
|
||||||
],
|
],
|
||||||
"presets": [
|
"presets": [
|
||||||
["@babel/preset-env", {
|
["@babel/preset-env", {
|
||||||
"useBuiltIns": false,
|
"useBuiltIns": false,
|
||||||
"targets": {
|
"targets": {
|
||||||
"esmodules": true,
|
"esmodules": true
|
||||||
},
|
}
|
||||||
}]
|
}]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
14
.gitignore
vendored
14
.gitignore
vendored
@ -2,6 +2,16 @@ old/
|
|||||||
build/
|
build/
|
||||||
/node_modules
|
/node_modules
|
||||||
/*.log
|
/*.log
|
||||||
/dist
|
/dist*
|
||||||
/dist-zip
|
|
||||||
/uw-git_keys
|
/uw-git_keys
|
||||||
|
/untracked-assets
|
||||||
|
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
*.kate-swp
|
||||||
|
|
||||||
|
src/res/img/git-ignore/
|
||||||
|
|
||||||
|
test/debug-configs/
|
||||||
|
|
||||||
|
debugging-resources/
|
||||||
|
|||||||
@ -1,23 +0,0 @@
|
|||||||
image: node:current
|
|
||||||
|
|
||||||
cache:
|
|
||||||
paths:
|
|
||||||
- node_modules/
|
|
||||||
- .yarn
|
|
||||||
|
|
||||||
stages:
|
|
||||||
- prep
|
|
||||||
- build
|
|
||||||
- pack
|
|
||||||
|
|
||||||
install deps:
|
|
||||||
stage: prep
|
|
||||||
script: yarn install
|
|
||||||
|
|
||||||
build:
|
|
||||||
stage: build
|
|
||||||
script: npm run build
|
|
||||||
|
|
||||||
create zip:
|
|
||||||
stage: pack
|
|
||||||
script: npm run build-zip
|
|
||||||
8
.vscode/extensions.json
vendored
Normal file
8
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"hollowtree.vue-pack",
|
||||||
|
"msjsdiag.debugger-for-chrome",
|
||||||
|
"firefox-devtools.vscode-firefox-debug",
|
||||||
|
"msjsdiag.debugger-for-edge"
|
||||||
|
]
|
||||||
|
}
|
||||||
35
.vscode/launch.json
vendored
35
.vscode/launch.json
vendored
@ -4,14 +4,40 @@
|
|||||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"configurations": [
|
"configurations": [
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "Launch addon",
|
"type": "chrome",
|
||||||
|
"request": "attach",
|
||||||
|
"name": "Attach to Chrome",
|
||||||
|
"port": 9222,
|
||||||
|
"urlFilter": "http://*/*",
|
||||||
|
"webRoot": "${workspaceFolder}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "firefox",
|
||||||
|
"request": "attach",
|
||||||
|
"name": "Attach (firefox)",
|
||||||
|
"pathMappings": [
|
||||||
|
{
|
||||||
|
"url": "webpack:///ext",
|
||||||
|
"path": "${workspaceFolder}/src/ext"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Launch addon (firefox)",
|
||||||
"type": "firefox",
|
"type": "firefox",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"port":6000,
|
"port": 6000,
|
||||||
"reAttach": true,
|
"reAttach": true,
|
||||||
"addonType": "webExtension",
|
"addonType": "webExtension",
|
||||||
"addonPath": "${workspaceFolder}/dist",
|
"addonPath": "${workspaceFolder}/dist-ff",
|
||||||
|
"pathMappings": [
|
||||||
|
{
|
||||||
|
"url": "webpack:///ext",
|
||||||
|
"path": "${workspaceFolder}/src/ext"
|
||||||
|
},
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"firefox": {
|
"firefox": {
|
||||||
@ -19,5 +45,8 @@
|
|||||||
"firefoxArgs": [
|
"firefoxArgs": [
|
||||||
"--start-debugger-server"
|
"--start-debugger-server"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"chrome": {
|
||||||
|
"executable": "/usr/bin/google-chrome-stable --remote-debugging-port=9222",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
86
.vscode/settings.json
vendored
86
.vscode/settings.json
vendored
@ -1,25 +1,109 @@
|
|||||||
{
|
{
|
||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
|
"aard",
|
||||||
|
"allowtransparency",
|
||||||
"ardetector",
|
"ardetector",
|
||||||
|
"autodetect",
|
||||||
"autodetection",
|
"autodetection",
|
||||||
|
"beforeunload",
|
||||||
"blackbar",
|
"blackbar",
|
||||||
|
"blackbars",
|
||||||
"blackframe",
|
"blackframe",
|
||||||
|
"bruteforce",
|
||||||
"canvas",
|
"canvas",
|
||||||
|
"clickthrough",
|
||||||
|
"cmpa",
|
||||||
|
"cmpb",
|
||||||
|
"com",
|
||||||
|
"comms",
|
||||||
|
"csui",
|
||||||
|
"ctab",
|
||||||
|
"cycleable",
|
||||||
|
"decycle",
|
||||||
|
"dinked",
|
||||||
|
"dinks",
|
||||||
|
"disneyplus",
|
||||||
|
"endregion",
|
||||||
"equalish",
|
"equalish",
|
||||||
|
"fith",
|
||||||
|
"fitw",
|
||||||
|
"fuckup",
|
||||||
|
"fucky",
|
||||||
|
"geoblocked",
|
||||||
|
"geoblocking",
|
||||||
|
"gfycat",
|
||||||
|
"gmail",
|
||||||
|
"guardline",
|
||||||
|
"han",
|
||||||
|
"haram",
|
||||||
|
"Heebo",
|
||||||
|
"hoverable",
|
||||||
|
"iframe",
|
||||||
|
"imgur",
|
||||||
"insta",
|
"insta",
|
||||||
|
"jsoneditor",
|
||||||
|
"jwplayer",
|
||||||
|
"kavin",
|
||||||
|
"letterboxed",
|
||||||
|
"manjaro",
|
||||||
|
"mdicon",
|
||||||
|
"mdijs",
|
||||||
|
"metaivi",
|
||||||
|
"minification",
|
||||||
|
"mitigations",
|
||||||
|
"mstefan",
|
||||||
|
"nogrow",
|
||||||
|
"noshrink",
|
||||||
|
"outro",
|
||||||
|
"PILLARBOX",
|
||||||
|
"PILLARBOXED",
|
||||||
|
"polyfill",
|
||||||
|
"problemo",
|
||||||
"recursing",
|
"recursing",
|
||||||
"reddit",
|
"reddit",
|
||||||
|
"rescan",
|
||||||
"resizer",
|
"resizer",
|
||||||
|
"resizers",
|
||||||
|
"SCPI",
|
||||||
|
"scrollbar",
|
||||||
|
"shitiness",
|
||||||
|
"smallcaps",
|
||||||
|
"spaghettified",
|
||||||
|
"suboption",
|
||||||
|
"subscan",
|
||||||
|
"tabitem",
|
||||||
|
"tablist",
|
||||||
|
"tamius",
|
||||||
"textbox",
|
"textbox",
|
||||||
|
"ultrawide",
|
||||||
|
"ultrawidify",
|
||||||
|
"unmark",
|
||||||
|
"unmarking",
|
||||||
|
"unsets",
|
||||||
|
"unshift",
|
||||||
|
"uwid",
|
||||||
|
"uwui",
|
||||||
"videodata",
|
"videodata",
|
||||||
"youtube"
|
"vids",
|
||||||
|
"vuejs",
|
||||||
|
"vuex",
|
||||||
|
"wakanim",
|
||||||
|
"webextension",
|
||||||
|
"webextensions",
|
||||||
|
"wuckies",
|
||||||
|
"wucky",
|
||||||
|
"ycenter",
|
||||||
|
"youtube",
|
||||||
|
"YYMM"
|
||||||
],
|
],
|
||||||
"cSpell.ignoreWords": [
|
"cSpell.ignoreWords": [
|
||||||
|
"abcdefghijklmnopqrstuvwxyz",
|
||||||
"autoar",
|
"autoar",
|
||||||
"cheight",
|
"cheight",
|
||||||
"cwidth",
|
"cwidth",
|
||||||
"fcstart",
|
"fcstart",
|
||||||
"fctime",
|
"fctime",
|
||||||
|
"legacycd",
|
||||||
"ncol",
|
"ncol",
|
||||||
"nrow",
|
"nrow",
|
||||||
"tickrate",
|
"tickrate",
|
||||||
|
|||||||
293
CHANGELOG.md
293
CHANGELOG.md
@ -1,18 +1,295 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## v4.x
|
## v6.0 (current major)
|
||||||
|
|
||||||
### Plans for the future
|
### v6.4.0
|
||||||
|
* In-player UI now appears in full-screen even for websites that use top layer
|
||||||
|
* Embedded sites now inherit settings of the parent frame by default
|
||||||
|
* Setting inheritance/overriding is not thoroughly tested and may be full of edge cases.
|
||||||
|
* Added validation to custom aspect ratio entry menu. Corrected parsing of aspect ratios given in the X:Y format, even though aspect ratios should be ideally given as a single number.
|
||||||
|
* Autodetection can now scan for subtitles.
|
||||||
|
* New experimental autodetection (which is secretly just slightly modified subtitle check)
|
||||||
|
|
||||||
* Settings page looks ugly af right now. Maybe fix it some time later
|
### v6.3.0
|
||||||
* rework keyboard shortcuts so things like russian layouts don't mess thigns up
|
* Added zoom segment to in-player UI and popup.
|
||||||
* other bug fixes
|
* Fixed keyboard zoom
|
||||||
|
* Added additional zoom options. If you wonder how zoom options differ from crop, mess around and find out.
|
||||||
|
* Subdomains now inherit same settings as their parent domain by default
|
||||||
|
* Extension popup detects embedded content
|
||||||
|
* Added `www.youtube-nocookie.com` to "officially supported" list
|
||||||
|
|
||||||
QoL improvements for me:
|
### v6.2.5
|
||||||
|
|
||||||
* logging: allow to enable logging at will and export said logs to a file
|
* Popup appearance changed — UI advertisement panel was moved to the popup header
|
||||||
|
* Fixed the bug where popup wouldn't be showing the correct settings
|
||||||
|
* Fixed the bug where site settings would default to 'disabled', even if Extension default setting was not disabled.
|
||||||
|
* Added ability to export and import settings from file (ft. developer mode editor)
|
||||||
|
* [dev goodies] Added debug overlay for aard
|
||||||
|
* Fixed an issue where aspect ratio wouldn't get calculated correctly on youtube videos with native aspect ratios other than 16:9
|
||||||
|
* Fixed an issue that would crash the extension if video element didn't have a player element associated with it
|
||||||
|
* Fixed an issue where extension sometimes wouldn't work if video element was grafted/re-parented to a different element
|
||||||
|
|
||||||
### v4.3.1 (current)
|
### v6.2.4
|
||||||
|
* [#264](https://github.com/tamius-han/ultrawidify/issues/264) — fixed issue with white screen that affected some youtube users. Special thanks to [SnowyOwlNugget](https://github.com/SnowyOwlNugget">SnowyOwlNugget), who instead of whining provided the necessary information.
|
||||||
|
* Minor updates to the settings
|
||||||
|
* Switching between full screen, theater, and normal player now correctly enables and disables the extension according to the settings.
|
||||||
|
* Don't reset settings if updating conf patches fails.
|
||||||
|
* Darken in-player UI backgrounds until Chrome fixes its backdrop-filter bug
|
||||||
|
|
||||||
|
### v6.2.3
|
||||||
|
* Fixed default persistent mode
|
||||||
|
|
||||||
|
### v6.2.2
|
||||||
|
* Fixed the issue where stretching was not applied if no cropping was used
|
||||||
|
* Added way to manually enable or disable color scheme detection, as a compromise between #259 and #264
|
||||||
|
|
||||||
|
### v6.2.1
|
||||||
|
|
||||||
|
* Fixed offset issue on Netflix
|
||||||
|
* Fixed issue with overlay iframe not being transparent on twitter
|
||||||
|
|
||||||
|
### v6.2.0
|
||||||
|
|
||||||
|
* Parts of automatic aspect ratio detection use WebGL now. This should result in improved performance.
|
||||||
|
* In-player UI now indicates whether a site is having a problem with automatic aspect ratio detection.
|
||||||
|
* UI improvements
|
||||||
|
|
||||||
|
Since automatic aspect ratio detection had to be rewritten completely and since the new version needs to be out 5 days ago, there are some regressions in the automatic aspect ratio detection.
|
||||||
|
|
||||||
|
### v6.1.0
|
||||||
|
|
||||||
|
Was skipped due to how major the changes were.
|
||||||
|
|
||||||
|
### v6.0.1
|
||||||
|
|
||||||
|
* Fixed external links
|
||||||
|
|
||||||
|
### v6.0.0
|
||||||
|
|
||||||
|
Chrome only, because I needed to rush manifest v3 migration before ensuring things work in Firefox.
|
||||||
|
|
||||||
|
* In player UI
|
||||||
|
* New alignment options (can align video both vertically, as well as horizontally)
|
||||||
|
* Extension does a little bit of a better job differentiating between levels of support for a given site
|
||||||
|
* Changed how cropping and panning works. This should lead to fewer problems and better generic support.
|
||||||
|
* REGRESSION: no manual panning with shift + mouse movement
|
||||||
|
* "Player select" screen, which provides a GUI way for picking HTML element that acts as the video player area
|
||||||
|
|
||||||
|
Regressions and potential issues:
|
||||||
|
* Settings were largely not migrated from previous versions. This is as intended, since changes to cropping (and some other aspects)
|
||||||
|
rendered certain old settings obsolete.
|
||||||
|
* Extension can no longer discriminate between iframes — extension popup commands get sent to ALL iframes on page
|
||||||
|
* Extension probably can't discriminate between multiple videos on a single page
|
||||||
|
|
||||||
|
## v5.x
|
||||||
|
|
||||||
|
### v5.1.7
|
||||||
|
|
||||||
|
Firefox-only.
|
||||||
|
|
||||||
|
* When cropping and panning video, CSS' `transform: translate(x,y)` now uses integers _always_. Before that fix, `translate(x,y)` could be offset by x.5 px, and that half of a pixel introduced a 1px thick line on the portion of the left edge of the video.
|
||||||
|
|
||||||
|
### v5.1.7
|
||||||
|
|
||||||
|
* When aligning videos, ensure video is never translated by a fractional value
|
||||||
|
* As of recent nVidia driver update in Edge, Angle detection fails in a way that prevents popup from showing up. This problem should be fixed now.
|
||||||
|
|
||||||
|
The angle detection problem was fixed by disabling the angle detection check for now, as Chrome, et al. appear to have fixed buggy video hardware acceleration.
|
||||||
|
|
||||||
|
### v5.1.6
|
||||||
|
|
||||||
|
* Added new config for disney+ (courtesy of @MStefan99)
|
||||||
|
* Chrome hardware acceleration is bugged and cannot be worked around in-extension. Only way to fix this is for users to change their Google Chrome settings. Added naggathon with instructions into the extension popup.
|
||||||
|
|
||||||
|
### v5.1.5
|
||||||
|
|
||||||
|
* Fixed laginess in Chromium-based browsers on Windows. Details in [#199](https://github.com/tamius-han/ultrawidify/issues/199#issuecomment-1221383134)
|
||||||
|
|
||||||
|
### v5.1.4
|
||||||
|
|
||||||
|
* Fixed some problems with autodetection not returning to 16:9 when necessary if autodetection already changed aspect ratio ([#198](https://github.com/tamius-han/ultrawidify/issues/198))
|
||||||
|
|
||||||
|
### v5.1.3
|
||||||
|
|
||||||
|
* Fixed some problems with autodetection sometimes briefly resetting on dark frames ([#195](https://github.com/tamius-han/ultrawidify/issues/195), [#196](https://github.com/tamius-han/ultrawidify/issues/196))
|
||||||
|
|
||||||
|
### v5.1.2
|
||||||
|
|
||||||
|
* `set-extension-mode` turned into `set-ExtensionMode` at some point. This caused in "Enable this extension" options to vanish on certain setups.
|
||||||
|
* Blackframe tests now run on same data as main algorithm as opposed on a smaller sample (`drawImage()` calls are _very_ expensive even for a 16x9 sample).
|
||||||
|
|
||||||
|
### v5.1.1
|
||||||
|
|
||||||
|
* Fixed autodetection
|
||||||
|
|
||||||
|
### v5.1.0
|
||||||
|
|
||||||
|
* Re-enable logger
|
||||||
|
* Move aspect ratio autodetection to requestAnimationFrame
|
||||||
|
* Fix netflix
|
||||||
|
|
||||||
|
### v5.0.7
|
||||||
|
|
||||||
|
* Videos of square-ish aspect ratios on 1440p (and lower) resolutions now no longer get misaligned ([#162](https://github.com/tamius-han/ultrawidify/issues/162))
|
||||||
|
* Alignment of featured videos on youtube channel page should now also be fixed
|
||||||
|
|
||||||
|
### v5.0.6
|
||||||
|
* Added configuration for metaivi.com based on user feedback ([#160](https://github.com/tamius-han/ultrawidify/issues/160))
|
||||||
|
* Removed ExtConfPatches for versions < 4.5.0, because nobody should be using a build of this extension that's over a year old
|
||||||
|
|
||||||
|
### v5.0.5
|
||||||
|
|
||||||
|
* improved UX a bit
|
||||||
|
* Fixed white background on app.plex.tv ([#158](https://github.com/tamius-han/ultrawidify/issues/158))
|
||||||
|
|
||||||
|
### v5.0.4
|
||||||
|
* Attempt to fix disney+ again, courtesy of [@jwannebo](https://github.com/tamius-han/ultrawidify/issues/84#issuecomment-846334005) on github.
|
||||||
|
|
||||||
|
|
||||||
|
### v5.0.3
|
||||||
|
|
||||||
|
* Fixed the issue where the videos were sometimes offset up and left. Again.
|
||||||
|
* Fix the issue where correcting source stretch was squished incorrectly ([#153](https://github.com/tamius-han/ultrawidify/issues/153))
|
||||||
|
|
||||||
|
### v5.0.2
|
||||||
|
|
||||||
|
* When in full screen, the extension will assume player element dimensions are the same as the screen resolution. This should help with sites where ultrawidify doesn't correctly identify the player, as cropping generally doesn't work if player element is not identified. Old behaviour can be restored in advanced extension settings by toggling the "use player aspect ratio in fullscreen" checkbox under 'player detection settings'.
|
||||||
|
* Extension should now respect 'disable extension' option for real.
|
||||||
|
* Fixed the issue where player wouldn't get detected if video was wider than the player.
|
||||||
|
|
||||||
|
|
||||||
|
### v5.0.1
|
||||||
|
* Added an option for users to turn off (and/or configure) Chrome/Edge's zoom limiter.
|
||||||
|
|
||||||
|
### v5.0.0
|
||||||
|
|
||||||
|
There's been some big-ish changes under the hood:
|
||||||
|
|
||||||
|
* Migrate main scripts to typescript (vue is currently not included).
|
||||||
|
* webextension-polyfill is now used everywhere (if only because typescript throws a hissy fit with `browser` and `chrome` otherwise) ([#114](https://github.com/tamius-han/ultrawidify/issues/114))
|
||||||
|
* Fix some bugs that I didn't even know I had, but typescript kinda shone some light on them
|
||||||
|
* Manual zoom (Z/U unless sites override the two) should now work again (without automatic AR constantly overriding it). Same goes for panning. ([#135](https://github.com/tamius-han/ultrawidify/issues/135) & [#138](https://github.com/tamius-han/ultrawidify/issues/138))
|
||||||
|
* Fix issue when video would be scaled incorrectly if video element uses `height:auto`.
|
||||||
|
* **[5.0.0.1]** Fixed the issue where settings were reset on page load.
|
||||||
|
* **[5.0.0.1]** Fixed the issue where settings page wouldn't load.
|
||||||
|
## v4.x (current major)
|
||||||
|
|
||||||
|
### v4.5.3
|
||||||
|
|
||||||
|
* Provides workaround for the fullscreen stretching bug Chrome 88 (or a recent Windows 10 update) introduced for nVidia users using hardware acceleration on Windows 10. In order to mitigate this bug, Ultrawidify needs to keep a 5-10 px wide black border while watching videos in full screen. This bug is also present in Edge.
|
||||||
|
* **[4.5.3.1]** Fixed letterbox misalignment binding in settings (#134)
|
||||||
|
* **[4.5.3.2]** Fixed false 'autodetection not supported' notifications.
|
||||||
|
|
||||||
|
### v4.5.2
|
||||||
|
|
||||||
|
* Fixed the issue where videos would sometimes get misaligned while using hybrid stretch, except for real this time. ([#125](https://github.com/tamius-han/ultrawidify/issues/125))
|
||||||
|
* Improved DRM detection (the 'autodetection cannot work on this site' popup should now no longer show up on the sites where autodetection _can_ work)
|
||||||
|
|
||||||
|
### v4.5.1
|
||||||
|
|
||||||
|
* Fixed the misalignment issue on netflix ... hopefully.
|
||||||
|
* 'Site settings' tab should now work in Chrome as well ([#126](https://github.com/tamius-han/ultrawidify/issues/126))
|
||||||
|
* Popup interface now refreshes properly ([#127](https://github.com/tamius-han/ultrawidify/issues/127))
|
||||||
|
* Videos should now be scaled correctly when the display is narrower than video's native aspect ratio ([#118](https://github.com/tamius-han/ultrawidify/issues/118))
|
||||||
|
* Fullscreen videos on streamable are aligned correctly ([#116](https://github.com/tamius-han/ultrawidify/issues/118)).
|
||||||
|
* **[4.5.1.1]** Streamable fix broke old.reddit + RES on embeds from v.redd.it and streamable.com. We're now using an alternative implementation. ([#128](https://github.com/tamius-han/ultrawidify/issues/128))
|
||||||
|
* **[4.5.1.2]** Fixed the issue where videos would sometimes get misaligned while using hybrid stretch. ([#125](https://github.com/tamius-han/ultrawidify/issues/125))
|
||||||
|
* **[4.5.1.3]** Added fix for disney plus
|
||||||
|
* **[4.5.1.3]** Microsoft Edge has fixed the bugs that prevented the extension from working properly. Popup should no longer be shown.
|
||||||
|
|
||||||
|
|
||||||
|
### v4.5.0 (Current)
|
||||||
|
|
||||||
|
* Under the hood: migrated from vue2 to vue3, because optional chaining in templates is too OP.
|
||||||
|
* (On options page, section 'Action & shortcuts') Manual aspect ratio now supports entering custom ratios using '21/9' and '2.39:1' formats (as opposed to single number, e.g. '2.39') — [#121](https://github.com/tamius-han/ultrawidify/issues/121).
|
||||||
|
* Added config for wakanim.tv (special thanks to @saschanaz for doing the legwork — [#113](https://github.com/tamius-han/ultrawidify/issues/113))
|
||||||
|
* (In Firefox) When extension was placed in overflow menu, the popup was cut off. That should be fixed now. [#119](https://github.com/tamius-han/ultrawidify/issues/119)
|
||||||
|
* The extension will now show a notification when autodetection can't run due to DRM
|
||||||
|
* Videos on facebook and reddit no longer get shifted up and to the left for me (cropping most of the video off-screen), but I haven't been deliberately trying to fix that issue. If you experience that issue, please consider contacting me (via github or email) with a link to a problematic video.
|
||||||
|
|
||||||
|
### v4.4.10
|
||||||
|
|
||||||
|
* Video alignment should now work on Twitch — [#109](https://github.com/tamius-han/ultrawidify/issues/109)
|
||||||
|
* Videos should now align properly on Hulu while cropped — [#111](https://github.com/tamius-han/ultrawidify/issues/111) & via email
|
||||||
|
* Fixed a problem where changing certain settings would cause multiple instances of Ultrawidify to run on a page, effectively preventing some crop options to be set until reload. (possibly [#112](https://github.com/tamius-han/ultrawidify/issues/112)?)
|
||||||
|
* Fixed a problem where embedded videos would be misaligned after switching from full screen
|
||||||
|
* **[4.4.10.1]** Fixed cruncyhroll regression — [#109](https://github.com/tamius-han/ultrawidify/issues/115)
|
||||||
|
|
||||||
|
### v4.4.9
|
||||||
|
|
||||||
|
* Fixed the youtube alignment issue (previously fixed in v4.4.7.1-2), but this time for real (and in a bit more proper way)
|
||||||
|
* Fixed the bug where extension wouldn't work when URL specified a port (e.g. www.example.com:80)
|
||||||
|
* **[4.4.9.1]** removed source files from extension build in order to decrease package size
|
||||||
|
* **[4.4.9.2]** updated dependencies and stuff
|
||||||
|
|
||||||
|
In addition to that, as of 4.4.9.1 the build process ensures removal of `node_modules` before building the extension so we can have reproducible builds except for real this time. Hopefully.
|
||||||
|
|
||||||
|
### v4.4.8
|
||||||
|
|
||||||
|
* Fixed the bug where on pages with more than one video, the list of available videos in the extension popup wouldn't remove videos that are no longer displayed on site. This resulted in extension listing videos that were no longer on the page. Reboot or navigation would also not clear the list if navigating between various pages on the same host.
|
||||||
|
* Fixed the chrome-only bug where on sites with more than one video, the number wouldn't get hidden when the extension popup closed.
|
||||||
|
|
||||||
|
### v4.4.7
|
||||||
|
|
||||||
|
* Removed unnecessary font files and image files from the package.
|
||||||
|
* LoggerUI is now functional.
|
||||||
|
* **[4.4.7.1]** Additional CSS fixes
|
||||||
|
* **[4.4.7.1]** Bruteforce fix for youtube alignment issue
|
||||||
|
* **[4.4.7.2]** Bruteforce fix for youtube alignment issue — increase number of retries
|
||||||
|
|
||||||
|
### v4.4.6
|
||||||
|
|
||||||
|
* Ensured that Vue part of the content script (logger UI) only loads when necessary in order to fix breakage on certain sites (#96).
|
||||||
|
* Disabling (or enabling, if running in whitelist-only mode) specific sites used to not work (#91). This issue appears to have been fixed.
|
||||||
|
* Default stretch mode for sites is now probably being observed, too (#94).
|
||||||
|
* Fixed netflix (and possibly disney+ )
|
||||||
|
* It's been almost a month and Chrome Web Store still hasn't finished the review of the 4.4.4.1 (and 4.4.4.2) revisions because when it comes to incompetence, it's hard to expect anything less from Google. I've did some proverbial yelling at the support in hopes that Chrome version will finally see an update (disclaimer: when I said yelling I really mean a polite request, because support staff doesn't deserve abuse because a different department is utter shite at doing their jobs).
|
||||||
|
|
||||||
|
### v4.4.5
|
||||||
|
|
||||||
|
* Extension no longer requires `allTabs` and `webNavigation` permissions
|
||||||
|
* Some CSS on the debugger popup was not scoped, causing issues with some sites.
|
||||||
|
* Fix some additional issues with video alignment when changing video on autoplay
|
||||||
|
|
||||||
|
### v4.4.4
|
||||||
|
|
||||||
|
* Tab detection in extension popup has been made more accurate
|
||||||
|
* QoL: Added user-accessible logger (to make fixing sites I can't access a bit easier)
|
||||||
|
* Changed links to reflect my github username change
|
||||||
|
* **[4.4.4.1]** Fix broken extension popup.
|
||||||
|
* **[4.4.4.1]** Fix global/site settings not getting applied immediately/only getting applied after page reload.
|
||||||
|
* **[4.4.4.2]** Fix problem with video being offset while switching between full screen and non-fullscreen non-theater mode on Youtube
|
||||||
|
|
||||||
|
### v4.4.3
|
||||||
|
|
||||||
|
* Fixed conf patch for disney+ (hopefully) (v4.4.3.1: but for real)
|
||||||
|
* `Settings.save()` adds missing values to site config when saving extension configuration.
|
||||||
|
|
||||||
|
### v4.4.2
|
||||||
|
|
||||||
|
* New stretching modes that squish video to a specified aspect ratio. One of the two modes correct aspect ratio before cropping, the other corrects after cropping.
|
||||||
|
* Potential fix for disney+ — added an option that forces extension to force player re-detection.
|
||||||
|
* Fixed bug where certain custom actions that should be removable weren't removable
|
||||||
|
|
||||||
|
### v4.4.1
|
||||||
|
|
||||||
|
* Changes to player detection that fix issues with vk
|
||||||
|
* Extension tries to avoid setting aspect ratio pointlessly
|
||||||
|
* (Hopefully) fixed mailto: and reddit compose links.
|
||||||
|
* When reporting bugs, email/reddit template now automatically gathers browser, extension version and OS.
|
||||||
|
|
||||||
|
### v4.4.0
|
||||||
|
|
||||||
|
* Russian users (and users of other non-latin keyboard layouts) can now use keyboard shortcuts by default, without having to rebind them manually. (NOTE: if you've changed keyboard shortcuts manually, this change will ***NOT*** be applied to your configuration.)
|
||||||
|
* NOTE: when using non-latin layouts, 'zoom' shortcut (`z` by default) uses the position of 'Y' on QWERTY layout.
|
||||||
|
* Ability to preserve aspect ratio between different videos (applies to current page and doesn't survive proper page reloads)
|
||||||
|
* Changing aspect ratio now resets zooming and panning.
|
||||||
|
* Fixed bug where keyboard shortcuts would work while typing in certain text fields
|
||||||
|
* Fixed minor bug with autodetection
|
||||||
|
* **[4.4.0.1]** fixed mailto and reddit compose links. When reporting issues via e-mail or reddit, extension version, browser
|
||||||
|
and OS are automatically included in email/reddit template.
|
||||||
|
|
||||||
|
### v4.3.1
|
||||||
|
|
||||||
* Minor rework of settings page (actions & shortcuts section)
|
* Minor rework of settings page (actions & shortcuts section)
|
||||||
* Fixed bug that prevented settings page from opening
|
* Fixed bug that prevented settings page from opening
|
||||||
|
|||||||
39
CONTRIBUTING.md
Normal file
39
CONTRIBUTING.md
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# Contributing to Ultrawidify
|
||||||
|
|
||||||
|
Thank you for considering contributing to this project! We welcome contributions from the community and are grateful for your efforts.
|
||||||
|
|
||||||
|
## How to Contribute
|
||||||
|
|
||||||
|
0. Open an issue, where you introduce the feature you want to add or thing you want to fix
|
||||||
|
1. Fork the repository.
|
||||||
|
2. Create a new branch for your contribution.
|
||||||
|
3. Write readable code
|
||||||
|
4. Check that your additions do not break existing features
|
||||||
|
5. Open a pull request
|
||||||
|
|
||||||
|
## Code of Conduct
|
||||||
|
|
||||||
|
Please follow the code of conduct:
|
||||||
|
|
||||||
|
* don't be too much of an asshole
|
||||||
|
* language policing my code comments is not a worthwhile contribution, and is considered spam. If you're here to do that, fuck off. I'm looking at you, ScamOSS and co.
|
||||||
|
|
||||||
|
## Code Style
|
||||||
|
|
||||||
|
- Use formatting consistent with the rest of the project
|
||||||
|
- Write meaningful commit messages.
|
||||||
|
- Include comments where necessary to explain complex logic.
|
||||||
|
|
||||||
|
## Licensing & Contribution Agreement
|
||||||
|
|
||||||
|
By submitting a contribution (e.g., via pull request), you agree to the following:
|
||||||
|
|
||||||
|
- You grant the project maintainer(s) a non-exclusive, irrevocable, worldwide, royalty-free license to use, copy, modify, and distribute your contributions as part of the project.
|
||||||
|
- You certify that your contributions are your original work and that you have the right to submit them.
|
||||||
|
- You agree that your contributions are licensed under the same license as the rest of the project, unless explicitly stated otherwise.
|
||||||
|
|
||||||
|
**TL;DR:** once your code is in the project, there's no take-backsies.
|
||||||
|
|
||||||
|
## Questions?
|
||||||
|
|
||||||
|
The discussion board is right over there, three tabs to the right.
|
||||||
6
DOCUMENTATION.MD
Normal file
6
DOCUMENTATION.MD
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# Implementation details
|
||||||
|
|
||||||
|
## Enabling/disabling aspect ratio corrections
|
||||||
|
|
||||||
|
* Aspect ratios are changed by proxy. Extension attaches **a custom CSS class** to `video` and `player` elements.
|
||||||
|
* To prevent extension from affecting the appearance of a webpage, **it's sufficient to remove our custom CSS classes from `video` and `player` elements.**
|
||||||
32
Jenkinsfile
vendored
Normal file
32
Jenkinsfile
vendored
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// required jenkins plugins:
|
||||||
|
// * https://plugins.jenkins.io/git/
|
||||||
|
|
||||||
|
pipeline {
|
||||||
|
agent any
|
||||||
|
|
||||||
|
stages {
|
||||||
|
|
||||||
|
// stage('Check for changes') {
|
||||||
|
// sh "env.GIT_COMMIT != env.GIT_PREVIOUS_COMMIT"
|
||||||
|
// }
|
||||||
|
|
||||||
|
stage('Install dependencies') {
|
||||||
|
steps {
|
||||||
|
sh 'npm ci'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Build') {
|
||||||
|
steps {
|
||||||
|
sh 'npm run build-all'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Push to release server') {
|
||||||
|
steps {
|
||||||
|
sh "echo 'implement me pls!'"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
35
LICENSE.MD
Normal file
35
LICENSE.MD
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# License
|
||||||
|
|
||||||
|
Copyright (c) 2025 @tamius-han
|
||||||
|
|
||||||
|
The source code is provided on the "you can look, but you can't take" basis.
|
||||||
|
|
||||||
|
## Permissions
|
||||||
|
|
||||||
|
You are permitted to:
|
||||||
|
|
||||||
|
* You can view the code
|
||||||
|
* You can create forks and modify the code for personal use
|
||||||
|
* You can build and run modified versions of this project for personal use on your local machine
|
||||||
|
* Push modifications to your personal github fork
|
||||||
|
|
||||||
|
## Restrictions
|
||||||
|
|
||||||
|
You may NOT:
|
||||||
|
|
||||||
|
* Distribute this project or any modified versions outside of your personal Github fork
|
||||||
|
* Publish or distribute compiled binaries, builds, or packages of this project or any derivative work
|
||||||
|
* Use the code in commercial products, services, or other public-facing deployments.
|
||||||
|
* Sub-license, sell, or otherwise make the software or derivatives available to third parties.
|
||||||
|
|
||||||
|
## Contributions
|
||||||
|
|
||||||
|
By submitting a pull request or other contribution, you agree that:
|
||||||
|
|
||||||
|
- You grant the project maintainer(s) a non-exclusive, irrevocable, worldwide, royalty-free license to use, copy, modify, and distribute your contributions as part of the project.
|
||||||
|
- You certify that your contributions are your original work and that you have the right to submit them.
|
||||||
|
- You agree that your contributions are licensed under the same license as the rest of the project, unless explicitly stated otherwise.
|
||||||
|
|
||||||
|
## Disclaimer
|
||||||
|
|
||||||
|
This software is provided "as is", without warranty of any kind, express or implied. Use at your own risk.
|
||||||
23
MSS-CERTIFICATION-NOTES.md
Normal file
23
MSS-CERTIFICATION-NOTES.md
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
Microsoft edge extension store requires notes for certification. Because I'm too lazy to whip this text over and over and over and over and over and over again, I'm making it a copypasta.
|
||||||
|
|
||||||
|
|
||||||
|
---------------------------------------------------------------------------------------------
|
||||||
|
This extension is targeted at users of 21:9 monitors. If using 16:9 monitor, use youtube in THEATER MODE.
|
||||||
|
|
||||||
|
Not all videos need to have their aspect ratio corrected. Here is few examples of videos with problems this extension is trying to solve:
|
||||||
|
|
||||||
|
* Variable aspect ratio — autodetection testing grounds: https://www.youtube.com/watch?v=-IHzprfs6tw
|
||||||
|
* Static (but incorrect) aspect ratio: https://www.youtube.com/watch?v=BTETsm79D3A
|
||||||
|
|
||||||
|
Twitch example (collapse subscription & chat sidebars on 16:9, do not fullscreen) — aspect ratio switch happens at around 1:50:00 mark:
|
||||||
|
* https://www.twitch.tv/videos/330639009?t=1h49m45s
|
||||||
|
|
||||||
|
Autodetect has technological limitations and can incorrectly detect aspect ratio. This is acceptable — user should override it in such cases.
|
||||||
|
|
||||||
|
Functions labelled 'experimental' are experimental and may not function correctly.
|
||||||
|
|
||||||
|
Websites may interfere with keyboard shortcuts.
|
||||||
|
|
||||||
|
Brief video flicker when changing between fullscreen, non-fullscreen or when changing browser is inherent artifact caused by sites not being aware of extension and is thus considered acceptable.
|
||||||
|
|
||||||
|
Logger option does nothing (except give you a popup) unless you have a logger config handy. It is designed for debugging in case of bug reports.
|
||||||
@ -4,14 +4,28 @@
|
|||||||
|
|
||||||
The extension is built on a PC running Manjaro Linux. npm and node are installed from repositories/aur.
|
The extension is built on a PC running Manjaro Linux. npm and node are installed from repositories/aur.
|
||||||
|
|
||||||
|
### Software versions:
|
||||||
|
|
||||||
## Installing dependencies
|
Node/npm versions:
|
||||||
|
|
||||||
Run `npm ci`
|
```
|
||||||
|
node: %%NODE_VERSION%%
|
||||||
|
npm: %%NPM_VERSION%%
|
||||||
|
```
|
||||||
|
|
||||||
|
Linux (`uname -a`):
|
||||||
|
|
||||||
|
```
|
||||||
|
%%LINUX_VERSION%%
|
||||||
|
```
|
||||||
|
|
||||||
## Reproducing build
|
## Reproducing build
|
||||||
|
|
||||||
`npm run build`
|
Run the following commands to install dependencies and compile the firefox build:
|
||||||
|
|
||||||
The compiled code pops up in /dist.
|
```
|
||||||
|
npm ci
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
The compiled code pops up in `/dist-ff`.
|
||||||
|
|||||||
@ -14,4 +14,4 @@ todo
|
|||||||
|
|
||||||
#This extension asks for permission that isn't listed above.
|
#This extension asks for permission that isn't listed above.
|
||||||
|
|
||||||
Sometimes (and by 'sometimes' I mean 'way too often') I forget to update README files. If there's a permission that I haven't wrote an explanation for, please [open an issue](https://github.com/xternal7/ultrawidify/issues).
|
Sometimes (and by 'sometimes' I mean 'way too often') I forget to update README files. If there's a permission that I haven't wrote an explanation for, please [open an issue](https://github.com/tamius-han/ultrawidify/issues).
|
||||||
71
README.md
71
README.md
@ -1,8 +1,10 @@
|
|||||||
# Ultrawidify — aspect ratio fixer for youtube and netflix
|
# Ultrawidify — aspect ratio fixer for youtube and netflix
|
||||||
|
|
||||||
## Super TL;DR: I'm just looking for the install links, thanks
|
## Super TL;DR: I'm just looking for the install links, thanks
|
||||||
|
|
||||||
[Firefox](https://addons.mozilla.org/en/firefox/addon/ultrawidify/), [Chrome](https://chrome.google.com/webstore/detail/ultrawidify/dndehlekllfkaijdlokmmicgnlanfjbi), [Edge](#edge-speficic-limitations-important).
|
[Firefox](https://addons.mozilla.org/en/firefox/addon/ultrawidify/), [Chrome](https://chrome.google.com/webstore/detail/ultrawidify/dndehlekllfkaijdlokmmicgnlanfjbi), [Edge](https://microsoftedge.microsoft.com/addons/detail/ultrawidify/lmpgpgechmkkkehkihpiddbcbgibokbi).
|
||||||
|
|
||||||
|
There's also [nightly "builds"](https://stuff.lionsarch.tamius.net/ultrawidify/nightly/).
|
||||||
|
|
||||||
## TL;DR
|
## TL;DR
|
||||||
|
|
||||||
@ -11,11 +13,10 @@ If you own an ultrawide monitor, you have probably noticed that sometimes videos
|
|||||||

|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Known issues
|
## Known issues
|
||||||
|
|
||||||
* Netflix autodetection not working in Chrome, wontfix as issue is fundamentally unfixable.
|
* Netflix autodetection not working in Chrome and working poorly in Firefox. This problem happens because DRM, and happens on other sites utilizing DRM protection schemes. Don't expect Chrome support any time soon.
|
||||||
* Everything reported in [issues](https://github.com/xternal7/ultrawidify/issues)
|
* Everything reported in [issues](https://github.com/tamius-han/ultrawidify/issues)
|
||||||
|
|
||||||
### Limitations
|
### Limitations
|
||||||
|
|
||||||
@ -23,15 +24,14 @@ If you own an ultrawide monitor, you have probably noticed that sometimes videos
|
|||||||
* Autodetection is only correct 95% of the time, most of the time.
|
* Autodetection is only correct 95% of the time, most of the time.
|
||||||
* That new stretching mode wasn't thoroughly tested yet. Issues may be present. (Same with zoom)
|
* That new stretching mode wasn't thoroughly tested yet. Issues may be present. (Same with zoom)
|
||||||
* Enabling extension everywhere (as opposed to whitelisted sites) could break some websites.
|
* Enabling extension everywhere (as opposed to whitelisted sites) could break some websites.
|
||||||
* Edge has
|
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
* **Can be enabled or disabled on per-site basis**
|
* **Can be enabled or disabled on per-site basis**
|
||||||
* **Crop video to fit screen** (no stretching. Supported aspect ratios: 21/9 (1:2.39), 16:9, 16:10, _one (1) custom aspect ratio_)
|
* **Crop video to fit screen** (no stretching. Supported aspect ratios: 21/9 (1:2.39), 16:9, 16:10. It's possible to set additional aspect ratios, but settings GUI currently contains some mildly annoying bugs)
|
||||||
* **Automatic aspect ratio detection** (can be enabled/disabled entirely or on a per-site basis, separately of the extension. Autodetection in action: [youtube](https://www.youtube.com/watch?v=j2xn1WpbtCQ))
|
* **Automatic aspect ratio detection** (can be enabled/disabled entirely or on a per-site basis, separately of the extension. May not work on sites utilizing DRM schemes, such as Netflix et. al.). Autodetection in action: [youtube](https://www.youtube.com/watch?v=j2xn1WpbtCQ))
|
||||||
* **Supports Youtube theater mode**
|
* **Supports Youtube theater mode**
|
||||||
* **[EXPERIMENTAL!]** Stretch video to fit the screen (4 different approaches)
|
* **[EXPERIMENTAL!]** Stretch video to fit the screen
|
||||||
* **[EXPERIMENTAL!]** custom zooming and panning
|
* **[EXPERIMENTAL!]** custom zooming and panning
|
||||||
|
|
||||||
|
|
||||||
@ -39,19 +39,30 @@ If you own an ultrawide monitor, you have probably noticed that sometimes videos
|
|||||||
|
|
||||||
* Youtube
|
* Youtube
|
||||||
* Netflix
|
* Netflix
|
||||||
|
* Twitch
|
||||||
|
|
||||||
### Other sites
|
### Other sites
|
||||||
|
|
||||||
I am not actively testing extension on other sites. You can try your luck and enable extension for any unsupported site you stumble across via extension popup, but I make no guarantees it will work everywhere.
|
I am not actively testing extension on other sites. You can try your luck and enable extension for any unsupported site you stumble across via extension popup, but I make no guarantees it will work everywhere.
|
||||||
|
|
||||||
|
If extension doesn't work for a site I'm not testing on out of the box, follow [this wiki](https://github.com/tamius-han/ultrawidify/wiki/Fixing-site-incompatibilites-('Advanced-settings')). The 'quick and dirty' approach should work for most sites. (If you try doing things the proper way, you should really know what you're doing.)
|
||||||
|
|
||||||
### Installing this extension
|
### Installing this extension
|
||||||
|
|
||||||
You can download this extension from Firefox' and Chrome's extension stores:
|
You can download this extension from the relevant extension stores:
|
||||||
|
|
||||||
* [Firefox](https://addons.mozilla.org/en/firefox/addon/ultrawidify/)
|
* [Firefox](https://addons.mozilla.org/en/firefox/addon/ultrawidify/)
|
||||||
* [Chrome](https://chrome.google.com/webstore/detail/ultrawidify/dndehlekllfkaijdlokmmicgnlanfjbi)
|
* [Chrome](https://chrome.google.com/webstore/detail/ultrawidify/dndehlekllfkaijdlokmmicgnlanfjbi)
|
||||||
|
* [Edge](https://microsoftedge.microsoft.com/addons/detail/ultrawidify/lmpgpgechmkkkehkihpiddbcbgibokbi)
|
||||||
|
|
||||||
This extension also has a version for Microsoft Edge, but that requires a bit more effort. [See notes first](#edge-speficic-limitations-important).
|
Other browsers are not officially supported. If you're using a different Chromium-based browser, you can try installing the addon from the Chrome Web Store — but if things don't work, you're on your own.
|
||||||
|
|
||||||
|
### Nightly builds
|
||||||
|
|
||||||
|
* Nightly builds can be downloaded [here](https://stuff.lionsarch.tamius.net/ultrawidify/) as an unpacked extension that can
|
||||||
|
only be installed temporarily.
|
||||||
|
|
||||||
|
If I did anything during the day, the nightly version will be sorta-built at whatever my VPS provider thinks is 4AM CE(S)T.
|
||||||
|
|
||||||
# Beggathon (donations)
|
# Beggathon (donations)
|
||||||
|
|
||||||
@ -71,7 +82,7 @@ You can make a donation [via Paypal](https://www.paypal.me/tamius).
|
|||||||
|
|
||||||
# The long version
|
# The long version
|
||||||
|
|
||||||
The technology has been here for a while, but plenty of people don't know how to properly encode a video (despite the fact [youtube has an article that explains aspect ratios](https://support.google.com/youtube/answer/6375112)). Plenty of people surprisingly includes major Holywood studios, such as [Marvel](https://www.youtube.com/watch?v=Ke1Y3P9D0Bc), [Disney](https://www.youtube.com/watch?v=yCOPJi0Urq4), [Dreamworks](https://www.youtube.com/watch?v=oKiYuIsPxYk), [Warner Brothers](https://www.youtube.com/watch?v=VYZ3U1inHA4), [Sony](https://www.youtube.com/watch?v=7BWWWQzTpNU), et cetera. You'd think that this is the one thing Holywood studios and people who make [music videos for a living](https://www.youtube.com/watch?v=c6Mx2mxpaCY) would know how to do right, but they don't. This extension is here to fix that.
|
The technology has been here for a while, but plenty of people don't know how to properly encode a video (despite the fact [youtube has an article that explains aspect ratios](https://support.google.com/youtube/answer/6375112)). Plenty of people surprisingly includes major Hollywood studios, such as [Marvel](https://www.youtube.com/watch?v=Ke1Y3P9D0Bc), [Disney](https://www.youtube.com/watch?v=yCOPJi0Urq4), [Dreamworks](https://www.youtube.com/watch?v=oKiYuIsPxYk), [Warner Brothers](https://www.youtube.com/watch?v=VYZ3U1inHA4), [Sony](https://www.youtube.com/watch?v=7BWWWQzTpNU), et cetera. You'd think that this is the one thing Hollywood studios and people who make [music videos for a living](https://www.youtube.com/watch?v=c6Mx2mxpaCY) would know how to do right, but they don't. This extension is here to fix that.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@ -218,13 +229,12 @@ The keyboard shortcuts have already been listed, but let's list them all again i
|
|||||||
|
|
||||||
### Rebinding keyboard shortcuts
|
### Rebinding keyboard shortcuts
|
||||||
|
|
||||||
is currently not possible. Settings page for this extension has been disabled sometime with 2.0 release (because it [broke](https://github.com/xternal7/ultrawidify/issues/16)), and fixing the setting page has been very low priority as I've had more important issues to work on.
|
is currently not possible. Settings page for this extension has been disabled sometime with 2.0 release (because it [broke](https://github.com/tamius-han/ultrawidify/issues/16)), and fixing the setting page has been very low priority as I've had more important issues to work on.
|
||||||
|
|
||||||
However, I do plan on implementing this feature. Hopefully by the end of the year, but given how consistently I've been breaking self-imposed deadlines and goals for this extension don't hold your breath. After all, [Hofstadter's a bitch](https://en.wikipedia.org/wiki/Hofstadter%27s_law).
|
However, I do plan on implementing this feature. Hopefully by the end of the year, but given how consistently I've been breaking self-imposed deadlines and goals for this extension don't hold your breath. After all, [Hofstadter's a bitch](https://en.wikipedia.org/wiki/Hofstadter%27s_law).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Plans for the future
|
## Plans for the future
|
||||||
|
|
||||||
1. Handle porting of extension settings between versions.
|
1. Handle porting of extension settings between versions.
|
||||||
@ -243,13 +253,22 @@ However, I do plan on implementing this feature. Hopefully by the end of the yea
|
|||||||
|
|
||||||
[Latest stable for Chrome — download from Chrome store](https://chrome.google.com/webstore/detail/ultrawidify/dndehlekllfkaijdlokmmicgnlanfjbi)
|
[Latest stable for Chrome — download from Chrome store](https://chrome.google.com/webstore/detail/ultrawidify/dndehlekllfkaijdlokmmicgnlanfjbi)
|
||||||
|
|
||||||
|
Edge version is currently not available, as Edge has some bugs that prevent this extension from working correctly. [Read more](https://github.com/tamius-han/ultrawidify/issues/117#issuecomment-747109695)
|
||||||
|
<!-- [Latest stable for Edge — Download from Microsoft store](https://microsoftedge.microsoft.com/addons/detail/lmpgpgechmkkkehkihpiddbcbgibokbi) -->
|
||||||
|
|
||||||
### Installing the current, github version
|
### Installing the current, github version
|
||||||
|
|
||||||
|
## Get pre-built version:
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
## Build from source
|
||||||
|
|
||||||
Requirements: npm, node.
|
Requirements: npm, node.
|
||||||
|
|
||||||
1. Clone this repo
|
1. Clone this repo
|
||||||
2. run `npm install`
|
2. run `npm install`
|
||||||
3. If using **Firefox,** run: `npm run watch:dev`. If using **Chrome,** run: `npm run watch-chrome:dev`. If using Edge, run: `npm run watch-edge:dev`.
|
3. If using **Firefox,** run: `npm run watch:dev`. If using **Chrome,** run: `npm run watch-chrome:dev`.
|
||||||
|
|
||||||
TODO: see if #3 already loads the extension in FF
|
TODO: see if #3 already loads the extension in FF
|
||||||
|
|
||||||
@ -258,28 +277,6 @@ TODO: see if #3 already loads the extension in FF
|
|||||||
4. Add temporary addon
|
4. Add temporary addon
|
||||||
5. Select `${ultrawidify_folder}/dist/manifest.json`
|
5. Select `${ultrawidify_folder}/dist/manifest.json`
|
||||||
|
|
||||||
|
|
||||||
# Edge-specific limitations (IMPORTANT!)
|
|
||||||
|
|
||||||
For various reasons — most notably, I refuse to pay Microsoft €14 for the privilege of developing shit for their outright broken browser (and in addition to that, the extension needs to go through review process as well) — this extension isn't going to appear on Microsoft Store. (And I do not permit anyone else to do that in my name either). Full rant on why I've made this decision can be found [here](https://github.com/xternal7/ultrawidify/issues/14#issuecomment-424903335).
|
|
||||||
|
|
||||||
As a result, you'll have to download the extension and install it manually. This approach has some downsides.
|
|
||||||
|
|
||||||
1. You'll get this popup after starting Edge. If you've already opened Youtube or Netflix, **you will have to reload the page (or navigate to somewhere else) in order for extension to start.**
|
|
||||||

|
|
||||||
|
|
||||||
2. Certain WebExtension APIs that I rely on are outright broken in Edge. This bug would cause global extension settings (tab: Extension settings) and per-site settings (tab: Site settings) to reset to default values every time you'd open the popup. As a result, _Extension settings_ and _Site settings_ tabs are disabled in Edge:
|
|
||||||

|
|
||||||
Unfortunate consequence of this is that you won't be able to enable this extension for sites other than Youtube and Netflix, but then again. Let's be honest. You're only using Edge for Netflix, so that's probably no big deal for you.
|
|
||||||
|
|
||||||
**It's also worth noting that I'm not actively maintaining the Edge fork, so it's a few versions behind.**
|
|
||||||
|
|
||||||
## Installing Ultrawidify in M$ Edge
|
|
||||||
|
|
||||||
1. Download the zip file from [here](https://github.com/xternal7/ultrawidify/tree/master/releases/edge)
|
|
||||||
2. Extract contents of the zip file in some folder. It really doesn't matter where, just keep it somewhere that won't be in your way when using your computer.
|
|
||||||
3. Follow the steps in [this guide](https://docs.microsoft.com/en-us/microsoft-edge/extensions/guides/adding-and-removing-extensions)
|
|
||||||
|
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
see changelog.md
|
see changelog.md
|
||||||
|
|||||||
28538
package-lock.json
generated
28538
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
121
package.json
121
package.json
@ -1,53 +1,88 @@
|
|||||||
{
|
{
|
||||||
"name": "ultravidify",
|
"name": "ultrawidify",
|
||||||
"version": "4.3.1",
|
"version": "6.3.998",
|
||||||
"description": "Aspect ratio fixer for youtube that works around some people's disability to properly encode 21:9 (and sometimes, 16:9) videos.",
|
"description": "Aspect ratio fixer for youtube and other sites, with automatic aspect ratio detection. Supports ultrawide and other ratios.",
|
||||||
"author": "Tamius Han <tamius.han@gmail.com>",
|
"author": "Tamius Han <tamius.han@gmail.com>",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "cross-env HMR=true npm run build:dev -- --watch",
|
"build": "npm run pre-build; cross-env NODE_ENV=production BROWSER=firefox CHANNEL=stable webpack",
|
||||||
"build": "cross-env NODE_ENV=production BROWSER=firefox webpack --hide-modules",
|
"build-all": "bash ./scripts/build-all.sh",
|
||||||
"build-chrome": "cross-env NODE_ENV=production BROWSER=chrome webpack --hide-modules",
|
"build-chrome": "cross-env NODE_ENV=production BROWSER=chrome CHANNEL=stable webpack",
|
||||||
"build-edge": "cross-env NODE_ENV=production BROWSER=edge webpack --hide-modules",
|
"build-chrome:dev": "cross-env NODE_ENV=development BROWSER=chrome webpack",
|
||||||
"build:dev": "cross-env NODE_ENV=development BROWSER=firefox webpack --hide-modules",
|
"build-edge": "cross-env NODE_ENV=production BROWSER=edge CHANNEL=stable webpack",
|
||||||
"build-chrome:dev": "cross-env NODE_ENV=development BROWSER=chrome webpack --hide-modules",
|
"build-nightly": "cross-env NODE_ENV=development BROWSER=firefox CHANNEL=nightly webpack",
|
||||||
"build-edge:dev": "cross-env NODE_ENV=development BROWSER=edge webpack --hide-modules",
|
"build-nightly-chrome": "cross-env NODE_ENV=development BROWSER=chrome CHANNEL=nightly webpack",
|
||||||
"build-all": "rm ./dist-zip/uw-amo-source.zip; mv ./dist-zip/*.zip ./dist-zip/old/; npm run build; node scripts/build-zip.js ff; npm run build-chrome; node scripts/build-zip.js chrome; ./scripts/prepare-amo-source.sh",
|
"build-testing": "cross-env NODE_ENV=development BROWSER=firefox CHANNEL=testing webpack",
|
||||||
|
"build-testing-chrome": "cross-env NODE_ENV=development BROWSER=chrome CHANNEL=testing webpack",
|
||||||
"build-zip": "node scripts/build-zip.js",
|
"build-zip": "node scripts/build-zip.js",
|
||||||
"watch": "npm run build -- --watch",
|
"build:dev": "webpack",
|
||||||
"watch-chrome": "npm run build-chrome -- --watch",
|
"dev": "cross-env NODE_ENV=development CHANNEL=dev concurrently \"cross-env BROWSER=firefox npm run build:dev -- --watch\" \"cross-env BROWSER=chrome npm run build:dev -- --watch\" \"cross-env BROWSER=edge npm run build:dev -- --watch\"",
|
||||||
"watch-edge": "npm run build-edge -- --watch",
|
"dev:windows": "cross-env NODE_ENV=development CHANNEL=dev concurrently \"cross-env BROWSER=firefox npm run build:dev -- -w --watch-poll\" \"cross-env BROWSER=chrome npm run build:dev -- -w --watch-poll\" \"cross-env BROWSER=edge npm run build:dev -- -w --watch-poll\"",
|
||||||
"watch:dev": "cross-env HMR=true npm run build:dev -- --watch",
|
"dev-ff": "rm -rf ./dist-ff && cross-env NODE_ENV=development CHANNEL=dev BROWSER=firefox npm run build:dev -- --watch",
|
||||||
"watch-chrome:dev": "cross-env HMR=true npm run build-chrome:dev -- --watch",
|
"dev-chrome": "rm -rf ./dist-chrome && cross-env NODE_ENV=development CHANNEL=dev BROWSER=chrome npm run build:dev -- --watch",
|
||||||
"watch-edge:dev": "cross-env HMR=true npm run build-edge:dev -- --watch"
|
"dev-edge": "rm -rf ./dist-edge && cross-env NODE_ENV=development CHANNEL=dev BROWSER=edge npm run build:dev -- --watch",
|
||||||
|
"pre-build": "rm -rf ./dist-ff; rm -rf ./dist-chrome; rm -rf ./dist-edge; rm -rf ./node_modules; npm ci",
|
||||||
|
"start": "npm run dev",
|
||||||
|
"start:windows": "npm run dev:windows"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/core-js": "^2.5.0",
|
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
||||||
"@types/es6-promise": "^3.3.0",
|
"@mdi/font": "^7.4.47",
|
||||||
"fs-extra": "^7.0.1",
|
"@mdi/js": "^7.4.47",
|
||||||
"vue": "^2.5.17",
|
"@types/resize-observer-browser": "^0.1.11",
|
||||||
"vuex": "^3.0.1",
|
"concurrently": "^9.2.1",
|
||||||
"vuex-webextensions": "^1.2.6"
|
"fs-extra": "^11.3.2",
|
||||||
|
"gl-matrix": "^3.4.4",
|
||||||
|
"json-cyclic": "1.0.2",
|
||||||
|
"json-difference": "^1.16.1",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"mdi-vue": "^3.0.13",
|
||||||
|
"typescript": "^5.9.3",
|
||||||
|
"vanilla-jsoneditor": "^3.10.0",
|
||||||
|
"vue": "^3.5.25",
|
||||||
|
"vue-style-loader": "^4.1.3",
|
||||||
|
"vuex": "^4.1.0",
|
||||||
|
"vuex-webextensions": "^1.3.3",
|
||||||
|
"webextension-polyfill": "^0.12.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.1.2",
|
"@babel/core": "^7.28.5",
|
||||||
"@babel/plugin-proposal-optional-chaining": "^7.0.0",
|
"@babel/preset-env": "^7.28.5",
|
||||||
"@babel/preset-env": "^7.1.0",
|
"@tailwindcss/postcss": "^4.1.17",
|
||||||
"archiver": "^3.0.0",
|
"@types/chrome": "0.1.31",
|
||||||
"babel-loader": "^8.0.2",
|
"@types/core-js": "^2.5.8",
|
||||||
"copy-webpack-plugin": "^4.5.3",
|
"@types/es6-promise": "^3.3.2",
|
||||||
"cross-env": "^5.2.0",
|
"@types/firefox": "0.0.34",
|
||||||
"css-loader": "^0.28.11",
|
"@types/lodash": "^4.17.21",
|
||||||
"ejs": "^2.6.1",
|
"@types/node": "^24.10.1",
|
||||||
"file-loader": "^1.1.11",
|
"@vue/cli": "^5.0.9",
|
||||||
"mini-css-extract-plugin": "^0.4.4",
|
"@vue/cli-plugin-typescript": "^5.0.9",
|
||||||
"node-sass": "^4.9.3",
|
"@vue/compiler-sfc": "^3.5.25",
|
||||||
"sass-loader": "^7.1.0",
|
"archiver": "^7.0.1",
|
||||||
"vue-loader": "^15.4.2",
|
"babel-loader": "^10.0.0",
|
||||||
"vue-template-compiler": "^2.5.17",
|
"babel-preset-es2020": "^1.0.2",
|
||||||
"web-ext-types": "^2.1.0",
|
"copy-webpack-plugin": "^13.0.1",
|
||||||
"webpack": "^4.20.2",
|
"cross-env": "^10.1.0",
|
||||||
"webpack-chrome-extension-reloader": "^0.8.3",
|
"css-loader": "^7.1.2",
|
||||||
"webpack-cli": "^3.1.2",
|
"ejs": "^3.1.10",
|
||||||
"webpack-shell-plugin": "^0.5.0"
|
"file-loader": "^6.2.0",
|
||||||
|
"mini-css-extract-plugin": "^2.9.4",
|
||||||
|
"postcss": "^8.5.6",
|
||||||
|
"postcss-import": "^16.1.1",
|
||||||
|
"postcss-nested": "^7.0.2",
|
||||||
|
"postcss-preset-env": "^10.4.0",
|
||||||
|
"postcss-strip-inline-comments": "^0.1.5",
|
||||||
|
"raw-loader": "^4.0.2",
|
||||||
|
"resolve-url-loader": "^5.0.0",
|
||||||
|
"tailwindcss": "^4.1.17",
|
||||||
|
"ts-loader": "^9.5.4",
|
||||||
|
"tsconfig-paths": "^4.2.0",
|
||||||
|
"vue-cli-plugin-vue-next": "~0.1.4",
|
||||||
|
"vue-loader": "^17.4.2",
|
||||||
|
"vue-tsc": "^3.1.5",
|
||||||
|
"web-ext-types": "^3.2.1",
|
||||||
|
"webpack": "^5.103.0",
|
||||||
|
"webpack-cli": "^6.0.1",
|
||||||
|
"webpack-shell-plugin": "^0.5.0",
|
||||||
|
"webpack-shell-plugin-next": "^2.3.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
10
paths.config.js
Normal file
10
paths.config.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
const path = require('path')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
'@src': path.resolve(__dirname, 'src'),
|
||||||
|
'@ui': path.resolve(__dirname, 'src/ui'),
|
||||||
|
'@components': path.resolve(__dirname, 'src/ui/components'),
|
||||||
|
'@res': path.resolve(__dirname, 'src/ui/res'),
|
||||||
|
'@': path.resolve(__dirname, 'src'),
|
||||||
|
};
|
||||||
|
|
||||||
24
postcss.config.js
Normal file
24
postcss.config.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
const path = require('path');
|
||||||
|
const aliases = require('./paths.config');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
plugins: [
|
||||||
|
require('postcss-import')({
|
||||||
|
resolve(id) {
|
||||||
|
for (const alias in aliases) {
|
||||||
|
if (id === alias || id.startsWith(alias + '/')) {
|
||||||
|
return path.resolve(
|
||||||
|
aliases[alias],
|
||||||
|
id.slice(alias.length + 1)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return id
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
require('@tailwindcss/postcss'),
|
||||||
|
require('postcss-nested'),
|
||||||
|
require('autoprefixer'),
|
||||||
|
],
|
||||||
|
};
|
||||||
Binary file not shown.
58
scripts/build-all.sh
Normal file
58
scripts/build-all.sh
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# NOTE: this script needs to be run with the npm run build-all
|
||||||
|
# command from the root directory of the project. Running it in
|
||||||
|
# any other way probably isn't going to work.
|
||||||
|
|
||||||
|
# pre-build steps:
|
||||||
|
|
||||||
|
echo "—————————————————————————————————————————————————————";
|
||||||
|
echo " BUILDING ALL ULTRAWIDIFY VERSIONS";
|
||||||
|
echo "—————————————————————————————————————————————————————";
|
||||||
|
|
||||||
|
mkdir -p ./build/old
|
||||||
|
npm run pre-build
|
||||||
|
|
||||||
|
echo "::"
|
||||||
|
echo "::"
|
||||||
|
echo "::"
|
||||||
|
echo ":: Pre-build complete. Moving old builds from dist-zip ..."
|
||||||
|
|
||||||
|
rm ./dist-zip/uw-amo-source.zip
|
||||||
|
mv -f ./dist-zip/*.zip ./build/old
|
||||||
|
|
||||||
|
echo "::"
|
||||||
|
echo "::"
|
||||||
|
echo "::"
|
||||||
|
echo ":: dist-zip is clean. Starting builds ..."
|
||||||
|
|
||||||
|
# lets force raise ram limit, but the improper way
|
||||||
|
# export NODE_OPTIONS=--max_old_space_size=4096
|
||||||
|
|
||||||
|
# build the version for each browser and create a zip afterwards
|
||||||
|
# step 1: define build functions
|
||||||
|
#function buildFF {
|
||||||
|
npm run build
|
||||||
|
node scripts/build-zip.js ff
|
||||||
|
#}
|
||||||
|
#function buildChrome {
|
||||||
|
npm run build-chrome
|
||||||
|
node scripts/build-zip.js chrome
|
||||||
|
#}
|
||||||
|
#function buildEdge {
|
||||||
|
npm run build-edge
|
||||||
|
node scripts/build-zip.js edge
|
||||||
|
#}
|
||||||
|
|
||||||
|
# step 2: execute them all at once
|
||||||
|
# buildFF &
|
||||||
|
# buildChrome &
|
||||||
|
# buildEdge &
|
||||||
|
|
||||||
|
# wait < <(jobs -p)
|
||||||
|
|
||||||
|
# prepare AMO source
|
||||||
|
# source code needs to be prepared AFTER
|
||||||
|
# the code has been built, to ensure that
|
||||||
|
# package-lock.json remains unchanged
|
||||||
|
./scripts/prepare-amo-source.sh
|
||||||
94
scripts/build-and-deploy.sh
Executable file
94
scripts/build-and-deploy.sh
Executable file
@ -0,0 +1,94 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# env variables to set:
|
||||||
|
#
|
||||||
|
# FORCE_BUILD set this variable to "true" if you wanna force build, even if build was
|
||||||
|
# triggered without any commits pushed to the directory
|
||||||
|
#
|
||||||
|
# BUILD_SCRIPT build-testing or build-nightly, could be any other from package.json tho
|
||||||
|
#
|
||||||
|
# RELEASE_SERVER target server (where to push built zips after they've been built)
|
||||||
|
# RELEASE_DIRECTORY base directory on the target server
|
||||||
|
# BUILD_CHANNEL_DIRECTORY directory for uploads, inside the release directory
|
||||||
|
#
|
||||||
|
# AMO_API_KEY needed if you want to sign and push extension to addons.mozilla.org
|
||||||
|
# AMO_API_SECRET -||-
|
||||||
|
|
||||||
|
echo "============= STARTING BUILD SCRIPT ============="
|
||||||
|
pwd
|
||||||
|
whoami
|
||||||
|
echo " ::: env dump"
|
||||||
|
echo " -> FORCE_BUILD: $FORCE_BUILD"
|
||||||
|
echo " -> BUILD_SCRIPT: $BUILD_SCRIPT"
|
||||||
|
echo " -> RELEASE_SERVER: $RELEASE_SERVER"
|
||||||
|
echo " -> RELEASE_DIRECTORY: $RELEASE_DIRECTORY"
|
||||||
|
echo " -> BUILD_CHANNEL_DIRECTORY: $BUILD_CHANNEL_DIRECTORY"
|
||||||
|
|
||||||
|
# don't build if nothing has changed, unless overriden via env variable
|
||||||
|
if [ ! -z "$GIT_COMMIT" ] ; then
|
||||||
|
if [ ! -z "$GIT_PREVIOUS_COMMIT" ] ; then
|
||||||
|
if [ "$GIT_COMMIT" == "$GIT_PREVIOUS_COMMIT" ] ; then
|
||||||
|
if [ $FORCE_BUILD == false ] ; then
|
||||||
|
echo "--------------------------------------------"
|
||||||
|
echo " Nothing has changed. Aborting build."
|
||||||
|
echo "--------------------------------------------"
|
||||||
|
exit 0;
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# let's raise RAM limit for npm command globally
|
||||||
|
NODE_OPTIONS=--max_old_space_size=4096
|
||||||
|
|
||||||
|
npm ci
|
||||||
|
|
||||||
|
rm -rf ./dist-zip || true # no big deal if ./dist-zip doesn't exist
|
||||||
|
mkdir dist-zip # create it back
|
||||||
|
|
||||||
|
#
|
||||||
|
# build firefox
|
||||||
|
#
|
||||||
|
npm run "${BUILD_SCRIPT}"
|
||||||
|
node --max-old-space-size=2048 scripts/build-zip.js ff nightly
|
||||||
|
# if [ ! -z "${AMO_API_KEY}" ] ; then
|
||||||
|
# if [ ! -z "${AMO_API_SECRET}" ] ; then
|
||||||
|
# web-ext sign --source-dir ./dist --api-key "${AMO_API_KEY}" --api-secret "${AMO_API_SECRET}"
|
||||||
|
# fi
|
||||||
|
# fi
|
||||||
|
|
||||||
|
#
|
||||||
|
# build chrome
|
||||||
|
#
|
||||||
|
npm run "${BUILD_SCRIPT}-chrome"
|
||||||
|
node --max-old-space-size=2048 scripts/build-zip.js chrome nightly
|
||||||
|
#
|
||||||
|
#./scripts/build-crx.sh
|
||||||
|
#
|
||||||
|
|
||||||
|
#
|
||||||
|
# build edge
|
||||||
|
#
|
||||||
|
npm run "${BUILD_SCRIPT}-edge"
|
||||||
|
node --max-old-space-size=2048 scripts/build-zip.js chrome nightly
|
||||||
|
|
||||||
|
|
||||||
|
######################################
|
||||||
|
# UPLOAD TO WEB SERVER
|
||||||
|
######################################
|
||||||
|
|
||||||
|
echo "--------------------------------------------"
|
||||||
|
echo " files ready for upload"
|
||||||
|
echo "--------------------------------------------"
|
||||||
|
echo ""
|
||||||
|
echo "Uploading to server ..."
|
||||||
|
# push all built stuff to the server
|
||||||
|
scp -i ~/.ssh/id_rsa -r ./dist-zip/* "ultrawidify-uploader@${RELEASE_SERVER}:${RELEASE_DIRECTORY}${BUILD_CHANNEL_DIRECTORY}"
|
||||||
|
|
||||||
|
|
||||||
|
######################################
|
||||||
|
# Build finished message
|
||||||
|
######################################
|
||||||
|
echo ""
|
||||||
|
echo "--------------------------------------------"
|
||||||
|
echo " BUILD FINISHED SUCCESSFULLY"
|
||||||
|
echo "--------------------------------------------"
|
||||||
45
scripts/build-crx.sh
Executable file
45
scripts/build-crx.sh
Executable file
@ -0,0 +1,45 @@
|
|||||||
|
#!/bin/bash -e
|
||||||
|
#
|
||||||
|
# Purpose: Pack a Chromium extension directory into crx format
|
||||||
|
# gotten from: https://web.archive.org/web/20180114090616/https://developer.chrome.com/extensions/crx#scripts
|
||||||
|
#
|
||||||
|
# Script assumes we're in basedir of the repository and that extension has been built and zipped to /dist-zip
|
||||||
|
# using the crx name we want (same name as zip, except different extension)
|
||||||
|
#
|
||||||
|
# also this doesn't check for errors ever so
|
||||||
|
#
|
||||||
|
|
||||||
|
echo "entering dist-zip"
|
||||||
|
cd ./dist-zip
|
||||||
|
|
||||||
|
zip=`ls | grep chrome.zip`
|
||||||
|
name="${zip%.*}"
|
||||||
|
crx="$name.crx"
|
||||||
|
key="../keys/chrome-nightly.pem" # this is also good for testing build
|
||||||
|
pub="$name.pub"
|
||||||
|
sig="$name.sig"
|
||||||
|
trap 'rm -f "$pub" "$sig" "$zip"' EXIT
|
||||||
|
|
||||||
|
# signature
|
||||||
|
openssl sha1 -sha1 -binary -sign "$key" < "$zip" > "$sig"
|
||||||
|
|
||||||
|
# public key
|
||||||
|
openssl rsa -pubout -outform DER < "$key" > "$pub" 2>/dev/null
|
||||||
|
|
||||||
|
byte_swap () {
|
||||||
|
# Take "abcdefgh" and return it as "ghefcdab"
|
||||||
|
echo "${1:6:2}${1:4:2}${1:2:2}${1:0:2}"
|
||||||
|
}
|
||||||
|
|
||||||
|
crmagic_hex="4372 3234" # Cr24
|
||||||
|
version_hex="0200 0000" # 2
|
||||||
|
pub_len_hex=$(byte_swap $(printf '%08x\n' $(ls -l "$pub" | awk '{print $5}')))
|
||||||
|
sig_len_hex=$(byte_swap $(printf '%08x\n' $(ls -l "$sig" | awk '{print $5}')))
|
||||||
|
(
|
||||||
|
echo "$crmagic_hex $version_hex $pub_len_hex $sig_len_hex" | xxd -r -p
|
||||||
|
cat "$pub" "$sig" "$zip"
|
||||||
|
) > "$crx"
|
||||||
|
echo "Wrote $crx"
|
||||||
|
|
||||||
|
echo "exiting dist-zip"
|
||||||
|
cd ..
|
||||||
@ -4,12 +4,8 @@ const fs = require('fs');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const archiver = require('archiver');
|
const archiver = require('archiver');
|
||||||
|
|
||||||
const DEST_DIR = path.join(__dirname, '../dist');
|
const extractExtensionData = (browserPostfix) => {
|
||||||
const DEST_ZIP_DIR = path.join(__dirname, '../dist-zip');
|
const extPackageJson = require(`../dist-${browserPostfix}/manifest.json`);
|
||||||
|
|
||||||
|
|
||||||
const extractExtensionData = () => {
|
|
||||||
const extPackageJson = require('../src/manifest.json');
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: extPackageJson.name,
|
name: extPackageJson.name,
|
||||||
@ -17,9 +13,9 @@ const extractExtensionData = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const makeDestZipDirIfNotExists = () => {
|
const makeDirIfNotExists = (dir) => {
|
||||||
if(!fs.existsSync(DEST_ZIP_DIR)) {
|
if(!fs.existsSync(dir)) {
|
||||||
fs.mkdirSync(DEST_ZIP_DIR);
|
fs.mkdirSync(dir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,13 +38,37 @@ const buildZip = (src, dist, zipFilename) => {
|
|||||||
|
|
||||||
const main = () => {
|
const main = () => {
|
||||||
const browser = process.argv[2];
|
const browser = process.argv[2];
|
||||||
|
const testingOrNightly = process.argv[3];
|
||||||
|
let browserPostfix;
|
||||||
|
if (browser == 'firefox') {
|
||||||
|
browserPostfix = 'ff';
|
||||||
|
} else {
|
||||||
|
browserPostfix = browser;
|
||||||
|
}
|
||||||
|
const destDir = path.join(__dirname, `../dist-${browserPostfix}`);
|
||||||
|
const zipDir = path.join(__dirname, '../dist-zip');
|
||||||
|
const {name, version} = extractExtensionData(browserPostfix);
|
||||||
|
|
||||||
const {name, version} = extractExtensionData();
|
// collapse spaces and dashes into single dash
|
||||||
const zipFilename = `${name}-${version}-${browser}.zip`;
|
const baseFilename = `${name.replace(/[ -]+/g, '-')}-${version}`;
|
||||||
|
|
||||||
makeDestZipDirIfNotExists();
|
let realZipDir;
|
||||||
|
|
||||||
buildZip(DEST_DIR, DEST_ZIP_DIR, zipFilename)
|
if (!!testingOrNightly) {
|
||||||
|
realZipDir = path.join(zipDir, version);
|
||||||
|
} else {
|
||||||
|
realZipDir = path.join(zipDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
const zipFilename = `${baseFilename}-${browser}.zip`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
makeDirIfNotExists(realZipDir, {recursive: true});
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to make directory.\nDirectory we wanted to make', realZipDir, '\nerror we got:\n', e)
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
buildZip(destDir, realZipDir, zipFilename)
|
||||||
.then(() => console.info('OK'))
|
.then(() => console.info('OK'))
|
||||||
.catch(console.err);
|
.catch(console.err);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,4 +2,46 @@
|
|||||||
|
|
||||||
# makes a zip file with human-readable source code that we need to upload to AMO
|
# makes a zip file with human-readable source code that we need to upload to AMO
|
||||||
# since webpack minifies stuff
|
# since webpack minifies stuff
|
||||||
zip -r dist-zip/uw-amo-source.zip README-AMO.md .babelrc package.json package-lock.json webpack.config.js src/
|
|
||||||
|
# first, we collect npm/node versions:
|
||||||
|
# (NOTE: the last bit is necessary to remove ANSI color codes from output)
|
||||||
|
NODE_VERSION=`node --version`
|
||||||
|
NPM_VERSION=`npm --version`
|
||||||
|
LINUX_VERSION="$(uname -a | sed 's/\//\\\//g')"
|
||||||
|
|
||||||
|
# copy REAMDE to /tmp for processing
|
||||||
|
cp README-AMO.md /tmp/README-AMO.md
|
||||||
|
|
||||||
|
# replace placeholders with proper software versions
|
||||||
|
sed -i "s/%%NODE_VERSION%%/${NODE_VERSION}/g" /tmp/README-AMO.md
|
||||||
|
sed -i "s/%%NPM_VERSION%%/${NPM_VERSION}/g" /tmp/README-AMO.md
|
||||||
|
sed -i "s/%%LINUX_VERSION%%/${LINUX_VERSION}/g" /tmp/README-AMO.md
|
||||||
|
|
||||||
|
# add files to archive
|
||||||
|
zip -r dist-zip/uw-amo-source.zip /tmp/README-AMO.md .babelrc package.json package-lock.json webpack.config.js scripts/ src/
|
||||||
|
|
||||||
|
# rename /tmp/README-AMO.md to README.md
|
||||||
|
printf "@ tmp/README-AMO.md\n@=README.md\n" | zipnote -w dist-zip/uw-amo-source.zip
|
||||||
|
|
||||||
|
# printout for debugging purposes:
|
||||||
|
echo ""
|
||||||
|
echo "—————— AMO SOURCE PREPARATION FINISHED ——————"
|
||||||
|
echo " Debug info:"
|
||||||
|
echo ""
|
||||||
|
echo "node --version:"
|
||||||
|
node --version
|
||||||
|
echo ""
|
||||||
|
echo "npm --version"
|
||||||
|
npm --version
|
||||||
|
echo ""
|
||||||
|
echo "uname -a"
|
||||||
|
uname -a
|
||||||
|
echo ""
|
||||||
|
echo ""
|
||||||
|
echo "extracted variables:"
|
||||||
|
echo "NODE_VERSION: ${NODE_VERSION}"
|
||||||
|
echo "NPM_VERSION: ${NPM_VERSION}"
|
||||||
|
echo "UNAME: ${LINUX_VERSION}"
|
||||||
|
echo ""
|
||||||
|
echo ""
|
||||||
|
echo "—————— EXTENSION PACKAGES READY FOR UPLOAD ——————"
|
||||||
|
|||||||
@ -3,11 +3,9 @@
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
|
||||||
const BUNDLE_DIR = path.join(__dirname, '../dist');
|
const BUNDLE_DIR = path.join(__dirname, `../dist-${process.env.BROWSER === 'firefox' ? 'ff' : process.env.BROWSER}`);
|
||||||
const bundles = [
|
const bundles = [
|
||||||
'background.js',
|
// 'csui/csui-popup.js',
|
||||||
'popup/popup.js',
|
|
||||||
'options/options.js',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const evalRegexForProduction = /;([a-z])=function\(\){return this}\(\);try{\1=\1\|\|Function\("return this"\)\(\)\|\|\(0,eval\)\("this"\)}catch\(t\){"object"==typeof window&&\(\1=window\)}/g;
|
const evalRegexForProduction = /;([a-z])=function\(\){return this}\(\);try{\1=\1\|\|Function\("return this"\)\(\)\|\|\(0,eval\)\("this"\)}catch\(t\){"object"==typeof window&&\(\1=window\)}/g;
|
||||||
|
|||||||
57
scripts/reproducability-test.sh
Executable file
57
scripts/reproducability-test.sh
Executable file
@ -0,0 +1,57 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
### VARIABLES
|
||||||
|
|
||||||
|
REPRODUCABILITY=5 # how many times we need to build with same hash
|
||||||
|
|
||||||
|
### TEST STARTS HERE ###
|
||||||
|
|
||||||
|
CURRENT_DIR=$(pwd)
|
||||||
|
|
||||||
|
if [[ $(echo $0 | awk -F"/" '{print NF-1}') -eq 1 ]] ; then
|
||||||
|
# we need to go one directory up
|
||||||
|
cd ..
|
||||||
|
fi
|
||||||
|
|
||||||
|
# cleanup after self if we already ran and previous test crashed or
|
||||||
|
# something before it cleaned up after itself.
|
||||||
|
rm -rf /tmp/uw-test-runs/* 2>/dev/null # we not interested if fails
|
||||||
|
|
||||||
|
# make new folder if it doesn't exist
|
||||||
|
mkdir -p /tmp/uw-test-runs
|
||||||
|
|
||||||
|
for ((run=0;run<$REPRODUCABILITY;run++)) ; do
|
||||||
|
echo ""
|
||||||
|
echo "---- run ${run} ----"
|
||||||
|
|
||||||
|
# save hash and then run build. Save hash.
|
||||||
|
OLD_HASH=$HASH
|
||||||
|
HASH=$(npm run build 2>/dev/null | grep "Hash:")
|
||||||
|
|
||||||
|
# move build file to /tmp, where it'll be saved for later
|
||||||
|
mv dist-ff /tmp/uw-test-runs/${run}
|
||||||
|
|
||||||
|
# skip comparisons with previous tests on first run,
|
||||||
|
# cos we don't have anything to compare against yet
|
||||||
|
if [[ $run -ne 0 ]] ; then
|
||||||
|
if [[ "$OLD_HASH" == "$HASH" ]] ; then
|
||||||
|
echo "Hashes ok${HASH##*:}"
|
||||||
|
else
|
||||||
|
echo "Webpack hashes do not match! (${OLD_HASH##*:} <---> ${HASH##*:})"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Diff test (no output=ok):"
|
||||||
|
diff -qr /tmp/uw-test-runs/${prev_run} /tmp/uw-test-runs/${run}
|
||||||
|
else
|
||||||
|
echo "Webpack hash: $HASH"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# save id of previous run
|
||||||
|
prev_run=$run
|
||||||
|
done
|
||||||
|
|
||||||
|
# clean up after self
|
||||||
|
rm -rf /tmp/uw-test-runs
|
||||||
|
|
||||||
|
# restore dir
|
||||||
|
cd "$CURRENT_DIR"
|
||||||
@ -1,248 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="action">
|
|
||||||
<div class="flex-row action-name-cmd-container">
|
|
||||||
<div class="">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="flex action-name">
|
|
||||||
<span v-if="action.cmd && action.cmd.length > 1 || action.cmd[0].action === 'set-ar' && action.cmd[0].arg === AspectRatio.Fixed" class="icon red" @click="removeAction()">🗙</span>
|
|
||||||
<span v-else class="icon transparent">🗙</span>
|
|
||||||
<span class="icon" @click="editAction()">🖉</span>
|
|
||||||
{{action.name}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="command-details">
|
|
||||||
<div class="flex flex-column cmd-container cd-pad">
|
|
||||||
<div class="flex bold">
|
|
||||||
Command:
|
|
||||||
</div>
|
|
||||||
<div class="flex cmdlist">
|
|
||||||
{{parseCommand(action.cmd)}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- SCOPES -->
|
|
||||||
<div class="flex flex-column scopes-container cd-pad">
|
|
||||||
<div class="flex bold">Popup tabs:</div>
|
|
||||||
|
|
||||||
<!-- global scope -->
|
|
||||||
<div v-if="action.scopes.global"
|
|
||||||
class="flex flex-row scope-row"
|
|
||||||
>
|
|
||||||
<div class="flex scope-scope">
|
|
||||||
Global:
|
|
||||||
</div>
|
|
||||||
<div class="flex scope-row-label scope-visible">
|
|
||||||
Visible? <span class="scope-row-highlight">{{action.scopes.global.show ? 'Yes' : 'No'}}</span>
|
|
||||||
</div>
|
|
||||||
<div v-if="action.scopes.global.show"
|
|
||||||
class="flex scope-row-label scope-button-label"
|
|
||||||
>
|
|
||||||
Button label?
|
|
||||||
<span class="scope-row-highlight">
|
|
||||||
{{action.scopes.global.label ? action.scopes.global.label : (action.label ? action.label : '')}}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex scope-row-label">
|
|
||||||
Keyboard shortcut:
|
|
||||||
<span class="scope-row-highlight">
|
|
||||||
{{action.scopes.global.shortcut ? parseActionShortcut(action.scopes.global.shortcut[0]) : 'None'}}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- site scope -->
|
|
||||||
<div v-if="action.scopes.site"
|
|
||||||
class="flex flex-row scope-row"
|
|
||||||
>
|
|
||||||
<div class="flex scope-scope">
|
|
||||||
Site:
|
|
||||||
</div>
|
|
||||||
<div class="flex scope-row-label scope-visible">
|
|
||||||
Visible? <span class="scope-row-highlight">{{action.scopes.site.show ? 'Yes' : 'No'}}</span>
|
|
||||||
</div>
|
|
||||||
<div v-if="action.scopes.site.show"
|
|
||||||
class="flex scope-row-label scope-button-label"
|
|
||||||
>
|
|
||||||
Button label?
|
|
||||||
<span class="scope-row-highlight">
|
|
||||||
{{action.scopes.site.label ? action.scopes.site.label : (action.label ? action.label : '')}}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex scope-row-label">
|
|
||||||
Keyboard shortcut:
|
|
||||||
<span class="scope-row-highlight">
|
|
||||||
{{action.scopes.site.shortcut ? parseActionShortcut(action.scopes.site.shortcut[0]) : 'None'}}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- page scope -->
|
|
||||||
<div v-if="action.scopes.page"
|
|
||||||
class="flex flex-row scope-row"
|
|
||||||
>
|
|
||||||
<div class="flex scope-scope">
|
|
||||||
Page:
|
|
||||||
</div>
|
|
||||||
<div class="flex scope-row-label scope-visible">
|
|
||||||
Visible? <span class="scope-row-highlight">{{action.scopes.page.show ? 'Yes' : 'No'}}</span>
|
|
||||||
</div>
|
|
||||||
<div v-if="action.scopes.page.show"
|
|
||||||
class="flex scope-row-label scope-button-label"
|
|
||||||
>
|
|
||||||
Button label?
|
|
||||||
<span class="scope-row-highlight">
|
|
||||||
{{action.scopes.page.label ? action.scopes.page.label : (action.label ? action.label : '')}}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex scope-row-label">
|
|
||||||
Keyboard shortcut:
|
|
||||||
<span class="scope-row-highlight">
|
|
||||||
{{action.scopes.page.shortcut ? parseActionShortcut(action.scopes.page.shortcut[0]) : 'None'}}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import Stretch from '../enums/stretch.enum';
|
|
||||||
import AspectRatio from '../enums/aspect-ratio.enum';
|
|
||||||
import KeyboardShortcutParser from '../js/KeyboardShortcutParser';
|
|
||||||
|
|
||||||
|
|
||||||
export default {
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
Stretch: Stretch,
|
|
||||||
AspectRatio: AspectRatio,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created () {
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
action: Object,
|
|
||||||
index: Number,
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
parseActionShortcut(shortcut) {
|
|
||||||
return KeyboardShortcutParser.parseShortcut(shortcut);
|
|
||||||
},
|
|
||||||
parseCommand(cmd) {
|
|
||||||
let cmdstring = '';
|
|
||||||
for (const c of cmd) {
|
|
||||||
cmdstring += `${c.action} ${c.arg}${c.persistent ? ' persistent' : ''}; `;
|
|
||||||
}
|
|
||||||
return cmdstring;
|
|
||||||
},
|
|
||||||
editAction() {
|
|
||||||
this.$emit('edit');
|
|
||||||
},
|
|
||||||
removeAction() {
|
|
||||||
this.$emit('remove');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
@import '../../res/css/colors.scss';
|
|
||||||
|
|
||||||
.action {
|
|
||||||
cursor: pointer;
|
|
||||||
position: relative;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action .command-details {
|
|
||||||
height: 0px;
|
|
||||||
max-height: 0px;
|
|
||||||
transition: max-height 0.5s ease;
|
|
||||||
overflow: hidden;
|
|
||||||
transition: height 0.5s ease;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
width: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action:hover .command-details {
|
|
||||||
height: auto;
|
|
||||||
max-height: 200px;
|
|
||||||
transition: max-height 0.5s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.command-details {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
width: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-name-cmd-container, .p1rem {
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cd-pad {
|
|
||||||
padding: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-name {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
font-weight: 300;
|
|
||||||
color: $text-normal;
|
|
||||||
width: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-name:hover, .action:hover .action-name {
|
|
||||||
color: lighten($primary-color, 20%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.red {
|
|
||||||
color: $primary-color !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cmd-container {
|
|
||||||
width: 13.37rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cmdlist {
|
|
||||||
font-family: 'Overpass Mono';
|
|
||||||
font-size: 0.9rem;
|
|
||||||
color: $text-dim;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bold {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scope-scope {
|
|
||||||
width: 5rem;
|
|
||||||
text-align: right !important;
|
|
||||||
color: $secondary-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scope-visible {
|
|
||||||
width: 7rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scope-button-label {
|
|
||||||
width: 16rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scope-row-label {
|
|
||||||
color: $text-dark;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scope-row-highlight {
|
|
||||||
color: $text-normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.transparent {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@ -1,76 +0,0 @@
|
|||||||
<template>
|
|
||||||
<tr>
|
|
||||||
<td class="cmd monospace">{{parseCommand(action.cmd)}}</td>
|
|
||||||
<td class="">{{action.label ? action.label : ""}}</td>
|
|
||||||
<td class="shortcut center-text">{{parseActionShortcut(action)}}</td>
|
|
||||||
<td class="center-text">
|
|
||||||
<div class="checkbox-container">
|
|
||||||
<span class="checkbox"
|
|
||||||
:class="{'checkbox-checked': (action.shortcut && action.shortcut.length &&
|
|
||||||
(action.shortcut[0].onMouseMove || action.shortcut[0].onClick ||
|
|
||||||
action.shortcut[0].onScroll))}"
|
|
||||||
></span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td class="center-text">
|
|
||||||
<div class="checkbox-container">
|
|
||||||
<span class="checkbox"
|
|
||||||
:class="{'checkbox-checked': action.popup_global}"
|
|
||||||
>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td class="center-text">
|
|
||||||
<div class="checkbox-container">
|
|
||||||
<span class="checkbox"
|
|
||||||
:class="{'checkbox-checked': action.popup_site}"
|
|
||||||
></span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td class="center-text">
|
|
||||||
<div class="checkbox-container">
|
|
||||||
<span class="checkbox"
|
|
||||||
:class="{'checkbox-checked': action.popup}"
|
|
||||||
></span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td class="center-text">
|
|
||||||
<div class="checkbox-container">
|
|
||||||
<span class="checkbox"
|
|
||||||
:class="{'checkbox-checked': action.ui}"
|
|
||||||
></span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>{{action.ui_path ? action.ui_path : ""}}</td>
|
|
||||||
</tr>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import Stretch from '../enums/stretch.enum';
|
|
||||||
import KeyboardShortcutParser from '../js/KeyboardShortcutParser'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
Stretch: Stretch
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created () {
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
action: Object
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
parseActionShortcut(action) {
|
|
||||||
return KeyboardShortcutParser.parseShortcut(action.shortcut[0]);
|
|
||||||
},
|
|
||||||
parseCommand(cmd) {
|
|
||||||
let cmdstring = '';
|
|
||||||
for(const c of cmd) {
|
|
||||||
cmdstring += `${c.action} ${c.arg}${c.persistent ? ' persistent' : ''}; `;
|
|
||||||
}
|
|
||||||
return cmdstring;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="button center-text flex"
|
|
||||||
:class="{'setting-selected': selected, 'w24': fixedWidth, 'flex-auto': !fixedWidth }"
|
|
||||||
>
|
|
||||||
{{label}}
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
fixedWidth: Boolean,
|
|
||||||
selected: Boolean,
|
|
||||||
label: String,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@ -1,53 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="qsb flex flex-row flex-cross-center flex-center flex-nogrow"
|
|
||||||
:class="{
|
|
||||||
'id': selector.startsWith('#'),
|
|
||||||
'class': selector.startsWith('.'),
|
|
||||||
}">
|
|
||||||
<div class="flex mt2px">{{selector}}</div> <span class="flex closeButton" @click="remove">×</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
selector: String,
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
remove() {
|
|
||||||
this.$emit('remove');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.mt2px {
|
|
||||||
margin-top: 7px;
|
|
||||||
}
|
|
||||||
.id {
|
|
||||||
border: 1px solid #d00;
|
|
||||||
background-color: rgba(216, 94, 24, 0.25)
|
|
||||||
}
|
|
||||||
.class {
|
|
||||||
border: 1px solid #33d;
|
|
||||||
background-color: rgba(69, 69, 255, 0.25)
|
|
||||||
}
|
|
||||||
.closeButton {
|
|
||||||
margin-top: 2px;
|
|
||||||
margin-left: 4px;
|
|
||||||
margin-right: 4px;
|
|
||||||
font-size: 1.5em;
|
|
||||||
color: #e00;
|
|
||||||
}
|
|
||||||
.closeButton:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
color: #fa6;
|
|
||||||
}
|
|
||||||
.qsb {
|
|
||||||
cursor:default;
|
|
||||||
margin-left: 3px;
|
|
||||||
margin-right: 3px;
|
|
||||||
margin-bottom: 2px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,87 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="flex flex-column">
|
|
||||||
<div v-if="!editing && !adding" class="flex flex-row">
|
|
||||||
<div class="">
|
|
||||||
<b>Query selector:</b> {{qs.string}}<br/>
|
|
||||||
<b>Additional CSS:</b> {{qs.css || 'no style rules'}}
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-column flex-nogrow">
|
|
||||||
<a @click="editing = true">Edit</a>
|
|
||||||
<a @click="$emit('delete')">Delete</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="flex flex-row">
|
|
||||||
<div class="flex flex-column">
|
|
||||||
<div class="flex flex-row">
|
|
||||||
<div class="flex label-secondary form-label">
|
|
||||||
Query selector:
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-input">
|
|
||||||
<input type="text"
|
|
||||||
v-model="qs.string"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-row">
|
|
||||||
<div class="flex label-secondary form-label">
|
|
||||||
Additional CSS:
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-input">
|
|
||||||
<input type="text"
|
|
||||||
v-model="qs.css"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<a v-if="editing" @click="cancelEdit">Cancel</a>
|
|
||||||
<a v-if="adding" @click="clear">Clear</a>
|
|
||||||
<a @click="save">Save</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
qs: {string: '', css: ''},
|
|
||||||
editing: false,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
querySelector: Object,
|
|
||||||
adding: {
|
|
||||||
type: Boolean,
|
|
||||||
required: false,
|
|
||||||
default: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
querySelector(val) {
|
|
||||||
this.qs = JSON.parse(JSON.stringify(val));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
save() {
|
|
||||||
if (this.adding) {
|
|
||||||
this.$emit('create', this.qs);
|
|
||||||
this.qs = {string: '', css: ''};
|
|
||||||
} else {
|
|
||||||
this.emit$('update', this.qs)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
cancelEdit() {
|
|
||||||
this.qs = JSON.parse(JSON.stringify(this.querySelector));
|
|
||||||
},
|
|
||||||
clear() {
|
|
||||||
this.qs = {string: '', css: ''};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="flex flex-column flex-center">
|
|
||||||
<div class="flex flex-self-center">
|
|
||||||
{{label}}
|
|
||||||
</div>
|
|
||||||
<div class="flex dark flex-self-center">
|
|
||||||
<small>
|
|
||||||
{{shortcut ? `(${shortcut})` : ''}}
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
label: String,
|
|
||||||
shortcut: String
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.center-text {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.dark {
|
|
||||||
opacity: 50%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
14
src/common/data/notifications.js
Normal file
14
src/common/data/notifications.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
let Notifications = Object.freeze({
|
||||||
|
'TEST_NOTIFICATION': {
|
||||||
|
icon: 'card-text',
|
||||||
|
text: 'This is a test notification.',
|
||||||
|
timeout: -1,
|
||||||
|
},
|
||||||
|
'AARD_DRM': {
|
||||||
|
icon: 'exclamation-triangle',
|
||||||
|
text: '<b>Autodetection may not be able to run on this video.</b> On sites that utilize DRM you will have to set aspect ratio manually.',
|
||||||
|
timeout: 5000,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default Notifications;
|
||||||
7
src/common/enums/AntiGradientMode.enum.ts
Normal file
7
src/common/enums/AntiGradientMode.enum.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
enum AntiGradientMode {
|
||||||
|
Disabled = 0,
|
||||||
|
Lax = 1,
|
||||||
|
Strict = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AntiGradientMode;
|
||||||
15
src/common/enums/AspectRatioType.enum.ts
Normal file
15
src/common/enums/AspectRatioType.enum.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
enum AspectRatioType {
|
||||||
|
Default = -3, // inherit from global settings
|
||||||
|
Cycle = -2,
|
||||||
|
Initial = -1, // page default
|
||||||
|
Reset = 0, // reset to initial
|
||||||
|
Automatic = 1, // we want to request automatic aspect ratio detection
|
||||||
|
FitWidth = 2, // legacy/dynamic = fit to width
|
||||||
|
FitHeight = 3, // legacy/dynamic = fit to height
|
||||||
|
Fixed = 4, // pre-determined aspect ratio
|
||||||
|
Manual = 5, // ratio achieved by zooming in/zooming out
|
||||||
|
AutomaticUpdate = 6, // set by Aard
|
||||||
|
Cover = 7, // replaces FitWidth and FitHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AspectRatioType;
|
||||||
9
src/common/enums/CropModePersistence.enum.ts
Normal file
9
src/common/enums/CropModePersistence.enum.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
enum CropModePersistence {
|
||||||
|
Default = -1,
|
||||||
|
Disabled = 0,
|
||||||
|
UntilPageReload = 1,
|
||||||
|
CurrentSession = 2,
|
||||||
|
Forever = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CropModePersistence;
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
enum EmbeddedContentSettingsOverridePolicy {
|
||||||
|
Always = 2,
|
||||||
|
UseAsDefault = 1,
|
||||||
|
Default = 0,
|
||||||
|
Never = -1,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EmbeddedContentSettingsOverridePolicy;
|
||||||
9
src/common/enums/ExtensionMode.enum.ts
Normal file
9
src/common/enums/ExtensionMode.enum.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
enum ExtensionMode {
|
||||||
|
Disabled = -1,
|
||||||
|
Default = 0,
|
||||||
|
FullScreen = 1,
|
||||||
|
Theater = 2,
|
||||||
|
All = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ExtensionMode;
|
||||||
5
src/common/enums/InputHandlingMode.enum.ts
Normal file
5
src/common/enums/InputHandlingMode.enum.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export enum InputHandlingMode {
|
||||||
|
Disabled = -1,
|
||||||
|
Default = 0,
|
||||||
|
Enabled = 1, // enable, but don't override site shortcuts
|
||||||
|
}
|
||||||
10
src/common/enums/LegacyExtensionMode.enum.ts
Normal file
10
src/common/enums/LegacyExtensionMode.enum.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
enum LegacyExtensionMode {
|
||||||
|
AutoDisabled = -2,
|
||||||
|
Disabled = -1,
|
||||||
|
Default = 0,
|
||||||
|
Whitelist = 1,
|
||||||
|
Basic = 2,
|
||||||
|
Enabled = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LegacyExtensionMode;
|
||||||
5
src/common/enums/PlayerDetectionMode.enum.ts
Normal file
5
src/common/enums/PlayerDetectionMode.enum.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export enum PlayerDetectionMode {
|
||||||
|
Auto = 0,
|
||||||
|
AncestorIndex = 1,
|
||||||
|
QuerySelectors = 2,
|
||||||
|
};
|
||||||
9
src/common/enums/SiteSupportLevel.enum.ts
Normal file
9
src/common/enums/SiteSupportLevel.enum.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export enum SiteSupportLevel {
|
||||||
|
OfficialSupport = 'official',
|
||||||
|
CommunitySupport = 'community',
|
||||||
|
UserDefined = 'user-defined',
|
||||||
|
BetaSupport = 'testing',
|
||||||
|
OfficialBlacklist = 'officially-disabled',
|
||||||
|
Unknown = 'unknown',
|
||||||
|
UserModified = 'modified',
|
||||||
|
}
|
||||||
11
src/common/enums/StretchType.enum.ts
Normal file
11
src/common/enums/StretchType.enum.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
enum StretchType {
|
||||||
|
NoStretch = 0,
|
||||||
|
Basic = 1,
|
||||||
|
Hybrid = 2,
|
||||||
|
Conditional = 3,
|
||||||
|
Fixed = 4,
|
||||||
|
FixedSource = 5,
|
||||||
|
Default = -1
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StretchType;
|
||||||
11
src/common/enums/VideoAlignmentType.enum.ts
Normal file
11
src/common/enums/VideoAlignmentType.enum.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
enum VideoAlignmentType {
|
||||||
|
Left = 0,
|
||||||
|
Center = 1,
|
||||||
|
Right = 2,
|
||||||
|
Top = 3,
|
||||||
|
Bottom = 4,
|
||||||
|
Default = -1,
|
||||||
|
Custom = -2,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default VideoAlignmentType;
|
||||||
@ -1,7 +0,0 @@
|
|||||||
var AntiGradientMode = Object.freeze({
|
|
||||||
Disabled: 0,
|
|
||||||
Lax: 1,
|
|
||||||
Strict: 2
|
|
||||||
});
|
|
||||||
|
|
||||||
export default AntiGradientMode;
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
var AspectRatio = Object.freeze({
|
|
||||||
Initial: -1,
|
|
||||||
Reset: 0,
|
|
||||||
Automatic: 1,
|
|
||||||
FitWidth: 2,
|
|
||||||
FitHeight: 3,
|
|
||||||
Fixed: 4,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default AspectRatio;
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
var ExtensionMode = Object.freeze({
|
|
||||||
AutoDisabled: -2,
|
|
||||||
Disabled: -1,
|
|
||||||
Default: 0,
|
|
||||||
Whitelist: 1,
|
|
||||||
Basic: 2,
|
|
||||||
Enabled: 3,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default ExtensionMode;
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
var Stretch = Object.freeze({
|
|
||||||
NoStretch: 0,
|
|
||||||
Basic: 1,
|
|
||||||
Hybrid: 2,
|
|
||||||
Conditional: 3,
|
|
||||||
Default: -1
|
|
||||||
});
|
|
||||||
|
|
||||||
export default Stretch;
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
var VideoAlignment = Object.freeze({
|
|
||||||
Left: 0,
|
|
||||||
Center: 1,
|
|
||||||
Right: 2,
|
|
||||||
Default: -1
|
|
||||||
});
|
|
||||||
|
|
||||||
export default VideoAlignment;
|
|
||||||
13
src/common/interfaces/ArInterface.ts
Normal file
13
src/common/interfaces/ArInterface.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import AspectRatioType from '../enums/AspectRatioType.enum';
|
||||||
|
|
||||||
|
export enum ArVariant {
|
||||||
|
Crop = undefined,
|
||||||
|
Zoom = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Ar {
|
||||||
|
type: AspectRatioType,
|
||||||
|
ratio?: number,
|
||||||
|
variant?: ArVariant,
|
||||||
|
offset?: number,
|
||||||
|
}
|
||||||
31
src/common/interfaces/ClientUiMenu.interface.ts
Normal file
31
src/common/interfaces/ClientUiMenu.interface.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { CommandInterface, InPlayerUIOptions } from '@src/common/interfaces/SettingsInterface';
|
||||||
|
|
||||||
|
export interface MenuItemBaseConfig {
|
||||||
|
label?: string;
|
||||||
|
subitems?: MenuItemConfig[];
|
||||||
|
command?: CommandInterface,
|
||||||
|
action?: () => void;
|
||||||
|
customHTML?: HTMLElement | string;
|
||||||
|
customId?: string;
|
||||||
|
customClassList?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MenuItemConfig = MenuItemBaseConfig & ({label: string} | {customHTML: HTMLElement | string});
|
||||||
|
|
||||||
|
export enum MenuPosition {
|
||||||
|
TopLeft = 'top-left',
|
||||||
|
Left = 'left',
|
||||||
|
BottomLeft = 'bottom-left',
|
||||||
|
Top = 'top',
|
||||||
|
Bottom = 'bottom',
|
||||||
|
TopRight = 'top-right',
|
||||||
|
Right = 'right',
|
||||||
|
BottomRight = 'bottom-right',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MenuConfig {
|
||||||
|
isGlobal?: boolean;
|
||||||
|
ui: InPlayerUIOptions;
|
||||||
|
options?: {forceShow?: boolean};
|
||||||
|
items: MenuItemConfig[];
|
||||||
|
}
|
||||||
43
src/common/interfaces/EventBusMessage.interface.ts
Normal file
43
src/common/interfaces/EventBusMessage.interface.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { CommsOrigin } from '@/ext/module/comms/CommsClient';
|
||||||
|
import type { Runtime } from 'chrome';
|
||||||
|
|
||||||
|
export interface EventBusCommand {
|
||||||
|
isGlobal?: boolean,
|
||||||
|
source?: any,
|
||||||
|
function: (commandData: any, context?: EventBusContext) => void | Promise<void>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EventBusMessage<T = unknown> {
|
||||||
|
command: string;
|
||||||
|
config?: T;
|
||||||
|
context?: EventBusContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EventBusContext {
|
||||||
|
stopPropagation?: boolean;
|
||||||
|
|
||||||
|
origin?: CommsOrigin;
|
||||||
|
frameUrl?: string;
|
||||||
|
|
||||||
|
// tab?: number;
|
||||||
|
// frame?: number;
|
||||||
|
// port?: string;
|
||||||
|
|
||||||
|
visitedBusses?: string[];
|
||||||
|
commandId?: string;
|
||||||
|
|
||||||
|
comms?: {
|
||||||
|
forwardTo?: 'all' | 'active' | 'popup' | 'contentScript' | 'all-frames';
|
||||||
|
sender?: Runtime.MessageSender;
|
||||||
|
port?: Runtime.Port;
|
||||||
|
// frame?: any;
|
||||||
|
sourceFrame?: {
|
||||||
|
tabId: number;
|
||||||
|
frameId: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
borderCrossings?: {
|
||||||
|
commsServer?: boolean,
|
||||||
|
iframe?: boolean,
|
||||||
|
};
|
||||||
|
}
|
||||||
8
src/common/interfaces/HostData.interface.ts
Normal file
8
src/common/interfaces/HostData.interface.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { ExtensionEnvironment } from '@src/common/interfaces/SettingsInterface';
|
||||||
|
|
||||||
|
export interface HostInfo {
|
||||||
|
host: string,
|
||||||
|
hasVideo: boolean,
|
||||||
|
minEnvironment: ExtensionEnvironment,
|
||||||
|
maxEnvironment: ExtensionEnvironment
|
||||||
|
};
|
||||||
6
src/common/interfaces/IframeTunnelPayload.interface.ts
Normal file
6
src/common/interfaces/IframeTunnelPayload.interface.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { EventBusMessage } from '@src/common/interfaces/EventBusMessage.interface';
|
||||||
|
|
||||||
|
export interface IframeTunnelPayload {
|
||||||
|
action: string,
|
||||||
|
payload: EventBusMessage,
|
||||||
|
}
|
||||||
19
src/common/interfaces/ScalingParamsBroadcast.interface.ts
Normal file
19
src/common/interfaces/ScalingParamsBroadcast.interface.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import VideoAlignmentType from '@src/common/enums/VideoAlignmentType.enum';
|
||||||
|
import { Ar } from '@src/common/interfaces/ArInterface';
|
||||||
|
import { Stretch } from '@src/common/interfaces/StretchInterface';
|
||||||
|
|
||||||
|
export interface ScalingParamsBroadcast {
|
||||||
|
effectiveZoom: {
|
||||||
|
x: number,
|
||||||
|
y: number
|
||||||
|
},
|
||||||
|
videoAlignment: {
|
||||||
|
x: VideoAlignmentType,
|
||||||
|
y: VideoAlignmentType,
|
||||||
|
xPos?: number,
|
||||||
|
yPos?: number,
|
||||||
|
}
|
||||||
|
lastAr: Ar,
|
||||||
|
manualZoom: boolean,
|
||||||
|
stretch: Stretch,
|
||||||
|
}
|
||||||
525
src/common/interfaces/SettingsInterface.ts
Normal file
525
src/common/interfaces/SettingsInterface.ts
Normal file
@ -0,0 +1,525 @@
|
|||||||
|
import { AardPollingOptions } from '@src/ext/module/aard/enums/aard-polling-options.enum'
|
||||||
|
import { AardSubtitleCropMode } from '@src/ext/module/aard/enums/aard-subtitle-crop-mode.enum'
|
||||||
|
import { SiteSupportLevel } from '@src/common/enums/SiteSupportLevel.enum';
|
||||||
|
import AspectRatioType from '@src/common/enums/AspectRatioType.enum'
|
||||||
|
import CropModePersistence from '@src/common/enums/CropModePersistence.enum'
|
||||||
|
import EmbeddedContentSettingsOverridePolicy from '@src/common/enums/EmbeddedContentSettingsOverridePolicy.enum'
|
||||||
|
import ExtensionMode from '@src/common/enums/ExtensionMode.enum'
|
||||||
|
import StretchType from '@src/common/enums/StretchType.enum'
|
||||||
|
import VideoAlignmentType from '@src/common/enums/VideoAlignmentType.enum'
|
||||||
|
import { PlayerDetectionMode } from '@src/common/enums/PlayerDetectionMode.enum';
|
||||||
|
import { InputHandlingMode } from '@src/common/enums/InputHandlingMode.enum';
|
||||||
|
import { MenuPosition } from '@src/common/interfaces/ClientUiMenu.interface';
|
||||||
|
|
||||||
|
export enum ExtensionEnvironment {
|
||||||
|
Normal = ExtensionMode.All,
|
||||||
|
Theater = ExtensionMode.Theater,
|
||||||
|
Fullscreen = ExtensionMode.FullScreen,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DevUiConfig {
|
||||||
|
aardDebugOverlay: {
|
||||||
|
showOnStartup: boolean,
|
||||||
|
showDetectionDetails: boolean,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface KeyboardShortcutInterface {
|
||||||
|
key?: string,
|
||||||
|
code?: string,
|
||||||
|
ctrlKey?: boolean,
|
||||||
|
metaKey?: boolean,
|
||||||
|
altKey?: boolean,
|
||||||
|
shiftKey?: boolean,
|
||||||
|
onKeyUp?: boolean,
|
||||||
|
onKeyDown?: boolean,
|
||||||
|
onMouseMove?: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ActionScopeInterface {
|
||||||
|
show: boolean,
|
||||||
|
label?: string, // example override, takes precedence over default label
|
||||||
|
shortcut?: KeyboardShortcutInterface[],
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RestrictionsSettings {
|
||||||
|
disableOnSmallPlayers?: boolean; // Whether ultrawidify should disable itself when the player is small
|
||||||
|
minAllowedWidth?: number; // if player is less than this many px wide, ultrawidify will disable itself
|
||||||
|
minAllowedHeight?: number; // if player is less than this many px tall, ultrawidify will disable itself
|
||||||
|
onlyAllowInFullscreen?: boolean; // if enabled, ultrawidify will be disabled when not in full screen regardless of what previous two options say
|
||||||
|
onlyAllowAutodetectionInFullScreen?: boolean; // if enabled, autodetection will only start once in full screen
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CommandInterface {
|
||||||
|
action: string,
|
||||||
|
label: string,
|
||||||
|
comment?: string,
|
||||||
|
arguments?: any,
|
||||||
|
shortcut?: KeyboardShortcutInterface,
|
||||||
|
internalOnly?: boolean,
|
||||||
|
actionId?: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SettingsReloadComponent = 'PlayerData' | 'VideoData';
|
||||||
|
export type SettingsReloadFlags = true | SettingsReloadComponent;
|
||||||
|
|
||||||
|
export interface AardSubtitleScanOptions {
|
||||||
|
subtitleCropMode: AardSubtitleCropMode,
|
||||||
|
resumeAfter: number, // resume after X ms of no subtitle
|
||||||
|
scanSpacing: number, // repeat subtitle scan every _n_ lines
|
||||||
|
scanMargin: number, // % of pixels (from edge to start area) that we skip while scanning.
|
||||||
|
// While technically anything between 0 and 0.5 is valid, the value
|
||||||
|
// should be somewhere between 0.1-0.3 in order for subtitle scan to
|
||||||
|
// work properly.
|
||||||
|
maxValidLetter: number, // if letter is longer than this, something's off.
|
||||||
|
subtitleSubpixelThresholdOn: number,
|
||||||
|
subtitleSubpixelThresholdOff: number,
|
||||||
|
|
||||||
|
minDetections: number,
|
||||||
|
minImageLineDetections: number,
|
||||||
|
|
||||||
|
maxPotentialSubtitleMisalignment: number, // how many pixels off-center can "potential subtitle" be
|
||||||
|
|
||||||
|
refiningScanSpacing: number, // must be base-2
|
||||||
|
refiningScanInitialIterations: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AardSettings {
|
||||||
|
aardType: 'webgl' | 'legacy' | 'auto';
|
||||||
|
useLegacy: boolean,
|
||||||
|
|
||||||
|
earlyStopOptions: {
|
||||||
|
stopAfterFirstDetection: boolean;
|
||||||
|
stopAfterTimeout: boolean;
|
||||||
|
stopTimeout: number;
|
||||||
|
},
|
||||||
|
|
||||||
|
polling: {
|
||||||
|
runInBackgroundTabs: AardPollingOptions;
|
||||||
|
runOnSmallVideos: AardPollingOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
disabledReason: string, // if automatic aspect ratio has been disabled, show reason
|
||||||
|
allowedMisaligned: number, // top and bottom letterbox thickness can differ by this much.
|
||||||
|
// Any more and we don't adjust ar.
|
||||||
|
allowedArVariance: number, // amount by which old ar can differ from the new (1 = 100%)
|
||||||
|
timers: { // autodetection frequency
|
||||||
|
playing: number, // while playing
|
||||||
|
playingReduced: number, // while video/player element has insufficient size
|
||||||
|
paused: number, // while paused
|
||||||
|
error: number, // after error
|
||||||
|
minimumTimeout: number,
|
||||||
|
tickrate: number, // 1 tick every this many milliseconds
|
||||||
|
},
|
||||||
|
|
||||||
|
subtitles: AardSubtitleScanOptions,
|
||||||
|
|
||||||
|
autoDisable: { // settings for automatically disabling the extension
|
||||||
|
onFirstChange: boolean,
|
||||||
|
ifNotChanged: boolean,
|
||||||
|
ifNotChangedTimeout: number,
|
||||||
|
ifSubtitles: boolean;
|
||||||
|
},
|
||||||
|
canvasDimensions: {
|
||||||
|
blackframeCanvas: { // smaller than sample canvas, blackframe canvas is used to recon for black frames
|
||||||
|
// it's not used to detect aspect ratio by itself, so it can be tiny af
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
},
|
||||||
|
sampleCanvas: { // size of image sample for detecting aspect ratio. Bigger size means more accurate results,
|
||||||
|
// at the expense of performance
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
blackLevels: {
|
||||||
|
defaultBlack: number, // By default, pixels darker than this are considered black.
|
||||||
|
// (If detection algorithm detects darker blacks, black is considered darkest detected pixel)
|
||||||
|
blackTolerance: number, // If pixel is more than this much brighter than blackLevel, it's considered not black
|
||||||
|
// It is not considered a valid image detection if gradient detection is enabled
|
||||||
|
imageDelta: number, // When gradient detection is enabled, pixels this much brighter than black skip gradient detection
|
||||||
|
}
|
||||||
|
sampling: {
|
||||||
|
edgePosition: number; // % of width (max 0.33). Pixels up to this far away from either edge may contain logo.
|
||||||
|
staticCols: number, // we take a column at [0-n]/n-th parts along the width and sample it
|
||||||
|
randomCols: number, // we add this many randomly selected columns to the static columns
|
||||||
|
staticRows: number, // forms grid with staticSampleCols. Determined in the same way. For black frame checks,
|
||||||
|
},
|
||||||
|
|
||||||
|
// pls deprecate and move things used
|
||||||
|
edgeDetection: {
|
||||||
|
gradientThreshold: number, // if more than this percentage (0-1) is detected as gradient, we mark edge as gradient
|
||||||
|
gradientTestMinDelta: number, // if difference between test row and before row is MORE than this -> not gradient
|
||||||
|
gradientTestMinDeltaAfter: number, // if difference between test row and after row is LESS than this -> not gradient
|
||||||
|
gradientTestMaxDeltaAfter: number, // if difference between test row and after row is MORE than this -> not gradient
|
||||||
|
maxLetterboxOffset: number, // Upper and lower letterbox can be different by this many (% of height)
|
||||||
|
|
||||||
|
minValidImage: number, // if more than this % (0-1) of row is image, we confirm image regardless of other criteria except gradient
|
||||||
|
maxEdgeSegments: number, // if edge has more than this many segments, we consider it unreliable
|
||||||
|
minEdgeSegmentSize: number,
|
||||||
|
averageEdgeThreshold: number, // average edge must be this many px
|
||||||
|
},
|
||||||
|
|
||||||
|
letterboxOrientationScan: {
|
||||||
|
letterboxLimit: number, // how many non-black pixels we can detect before ruling out letterbox
|
||||||
|
pillarboxLimit: number, // how many non-black pixels we can detect before ruling out pillarbox
|
||||||
|
}
|
||||||
|
|
||||||
|
pillarTest: {
|
||||||
|
ignoreThinPillarsPx: number, // ignore pillars that are less than this many pixels thick.
|
||||||
|
allowMisaligned: number // left and right edge can vary this much (%)
|
||||||
|
},
|
||||||
|
textLineTest: {
|
||||||
|
nonTextPulse: number, // if a single continuous pulse has this many non-black pixels, we aren't dealing
|
||||||
|
// with text. This value is relative to canvas width (%)
|
||||||
|
pulsesToConfirm: number, // this is a threshold to confirm we're seeing text.
|
||||||
|
pulsesToConfirmIfHalfBlack: number, // this is the threshold to confirm we're seeing text if longest black pulse
|
||||||
|
// is over 50% of the canvas width
|
||||||
|
testRowOffset: number // we test this % of height from detected edge
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AardLegacySettings {
|
||||||
|
aardType: 'webgl' | 'legacy' | 'auto';
|
||||||
|
|
||||||
|
earlyStopOptions: {
|
||||||
|
stopAfterFirstDetection: boolean;
|
||||||
|
stopAfterTimeout: boolean;
|
||||||
|
stopTimeout: number;
|
||||||
|
},
|
||||||
|
|
||||||
|
polling: {
|
||||||
|
runInBackgroundTabs: AardPollingOptions;
|
||||||
|
runOnSmallVideos: AardPollingOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
disabledReason: string, // if automatic aspect ratio has been disabled, show reason
|
||||||
|
allowedMisaligned: number, // top and bottom letterbox thickness can differ by this much.
|
||||||
|
// Any more and we don't adjust ar.
|
||||||
|
allowedArVariance: number, // amount by which old ar can differ from the new (1 = 100%)
|
||||||
|
timers: { // autodetection frequency
|
||||||
|
playing: number, // while playing
|
||||||
|
playingReduced: number, // while video/player element has insufficient size
|
||||||
|
paused: number, // while paused
|
||||||
|
error: number, // after error
|
||||||
|
minimumTimeout: number,
|
||||||
|
tickrate: number, // 1 tick every this many milliseconds
|
||||||
|
},
|
||||||
|
|
||||||
|
subtitles: AardSubtitleScanOptions,
|
||||||
|
|
||||||
|
autoDisable: { // settings for automatically disabling the extension
|
||||||
|
onFirstChange: boolean,
|
||||||
|
ifNotChanged: boolean,
|
||||||
|
ifNotChangedTimeout: number,
|
||||||
|
ifSubtitles: boolean;
|
||||||
|
},
|
||||||
|
canvasDimensions: {
|
||||||
|
blackframeCanvas: { // smaller than sample canvas, blackframe canvas is used to recon for black frames
|
||||||
|
// it's not used to detect aspect ratio by itself, so it can be tiny af
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
},
|
||||||
|
sampleCanvas: { // size of image sample for detecting aspect ratio. Bigger size means more accurate results,
|
||||||
|
// at the expense of performance
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
blackLevels: {
|
||||||
|
defaultBlack: number, // By default, pixels darker than this are considered black.
|
||||||
|
// (If detection algorithm detects darker blacks, black is considered darkest detected pixel)
|
||||||
|
blackTolerance: number, // If pixel is more than this much brighter than blackLevel, it's considered not black
|
||||||
|
// It is not considered a valid image detection if gradient detection is enabled
|
||||||
|
imageDelta: number, // When gradient detection is enabled, pixels this much brighter than black skip gradient detection
|
||||||
|
}
|
||||||
|
sampling: {
|
||||||
|
edgePosition: number; // % of width (max 0.33). Pixels up to this far away from either edge may contain logo.
|
||||||
|
staticCols: number, // we take a column at [0-n]/n-th parts along the width and sample it
|
||||||
|
randomCols: number, // we add this many randomly selected columns to the static columns
|
||||||
|
staticRows: number, // forms grid with staticSampleCols. Determined in the same way. For black frame checks,
|
||||||
|
},
|
||||||
|
|
||||||
|
// pls deprecate and move things used
|
||||||
|
edgeDetection: {
|
||||||
|
slopeTestWidth: number,
|
||||||
|
gradientTestSamples: number, // we check this many pixels below (or above) the suspected edge to check for gradient
|
||||||
|
gradientTestBlackThreshold: number, // if pixel in test sample is brighter than that, we aren't looking at gradient
|
||||||
|
gradientTestDeltaThreshold: number, // if delta between two adjacent pixels in gradient test exceeds this, it's not gradient
|
||||||
|
|
||||||
|
thresholds: {
|
||||||
|
edgeDetectionLimit: number, // during scanning of the edge, quit after edge gets detected at this many points
|
||||||
|
minQualitySingleEdge: number, // At least one of the detected must reach this quality
|
||||||
|
minQualitySecondEdge: number, // The other edge must reach this quality (must be smaller or equal to single edge quality)
|
||||||
|
}
|
||||||
|
|
||||||
|
gradientThreshold: number, // if more than this percentage (0-1) is detected as gradient, we mark edge as gradient
|
||||||
|
gradientTestMinDelta: number, // if difference between test row and before row is MORE than this -> not gradient
|
||||||
|
gradientTestMinDeltaAfter: number, // if difference between test row and after row is LESS than this -> not gradient
|
||||||
|
gradientTestMaxDeltaAfter: number, // if difference between test row and after row is MORE than this -> not gradient
|
||||||
|
maxLetterboxOffset: number, // Upper and lower letterbox can be different by this many (% of height)
|
||||||
|
|
||||||
|
// Previous iteration variables VVVV
|
||||||
|
sampleWidth: number, // we take a sample this wide for edge detection
|
||||||
|
detectionThreshold: number, // sample needs to have this many non-black pixels to be a valid edge
|
||||||
|
confirmationThreshold: number, //
|
||||||
|
singleSideConfirmationThreshold: number, // we need this much edges (out of all samples, not just edges) in order
|
||||||
|
// to confirm an edge in case there's no edges on top or bottom (other
|
||||||
|
// than logo, of course)
|
||||||
|
logoThreshold: number, // if edge candidate sits with count greater than this*all_samples, it can't be logo
|
||||||
|
// or watermark.
|
||||||
|
edgeTolerancePx?: number, // we check for black edge violation this far from detection point
|
||||||
|
edgeTolerancePercent?: number, // we check for black edge detection this % of height from detection point. unused
|
||||||
|
middleIgnoredArea: number, // we ignore this % of canvas height towards edges while detecting aspect ratios
|
||||||
|
minColsForSearch: number, // if we hit the edge of blackbars for all but this many columns (%-wise), we don't
|
||||||
|
// continue with search. It's pointless, because black edge is higher/lower than we
|
||||||
|
// are now. (NOTE: keep this less than 1 in case we implement logo detection)
|
||||||
|
edgeMismatchTolerancePx: number,// corners and center are considered equal if they differ by at most this many px
|
||||||
|
|
||||||
|
|
||||||
|
minValidImage: number, // if more than this % (0-1) of row is image, we confirm image regardless of other criteria except gradient
|
||||||
|
maxEdgeSegments: number, // if edge has more than this many segments, we consider it unreliable
|
||||||
|
minEdgeSegmentSize: number,
|
||||||
|
averageEdgeThreshold: number, // average edge must be this many px
|
||||||
|
},
|
||||||
|
|
||||||
|
letterboxOrientationScan: {
|
||||||
|
letterboxLimit: number, // how many non-black pixels we can detect before ruling out letterbox
|
||||||
|
pillarboxLimit: number, // how many non-black pixels we can detect before ruling out pillarbox
|
||||||
|
}
|
||||||
|
|
||||||
|
pillarTest: {
|
||||||
|
ignoreThinPillarsPx: number, // ignore pillars that are less than this many pixels thick.
|
||||||
|
allowMisaligned: number // left and right edge can vary this much (%)
|
||||||
|
},
|
||||||
|
textLineTest: {
|
||||||
|
nonTextPulse: number, // if a single continuous pulse has this many non-black pixels, we aren't dealing
|
||||||
|
// with text. This value is relative to canvas width (%)
|
||||||
|
pulsesToConfirm: number, // this is a threshold to confirm we're seeing text.
|
||||||
|
pulsesToConfirmIfHalfBlack: number, // this is the threshold to confirm we're seeing text if longest black pulse
|
||||||
|
// is over 50% of the canvas width
|
||||||
|
testRowOffset: number // we test this % of height from detected edge
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
interface DevSettings {
|
||||||
|
loadFromSnapshot: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InPlayerUIOptions {
|
||||||
|
activatorAlignment: MenuPosition,
|
||||||
|
minEnabledWidth: number, // don't show UI if player is narrower than % of screen width
|
||||||
|
minEnabledHeight: number, // don't show UI if player is narrower than % of screen height
|
||||||
|
activation: 'player' | 'trigger-zone' | 'distance' | 'none', // what needs to be hovered in order for UI to be visible
|
||||||
|
activateWithCtrl: boolean,
|
||||||
|
activationDistance: number,
|
||||||
|
activationDistanceUnits: '%' | 'px',
|
||||||
|
activatorPadding: {x: number, y: number}
|
||||||
|
activatorPaddingUnit: {x: '%' | 'px', y: '%' | 'px'},
|
||||||
|
triggerZoneDimensions: { // how large the trigger zone is (relative to player size)
|
||||||
|
width: number
|
||||||
|
height: number,
|
||||||
|
offsetX: number, // fed to translateX(offsetX + '%'). Valid range [-100, 0]
|
||||||
|
offsetY: number // fed to translateY(offsetY + '%'). Valid range [-100, 100]
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
interface SettingsInterface {
|
||||||
|
_updateFlags?: {
|
||||||
|
requireReload?: SettingsReloadFlags,
|
||||||
|
forSite?: string
|
||||||
|
}
|
||||||
|
dev: DevSettings,
|
||||||
|
|
||||||
|
aardLegacy: AardLegacySettings,
|
||||||
|
aard: AardSettings,
|
||||||
|
|
||||||
|
ui: {
|
||||||
|
inPlayer: InPlayerUIOptions,
|
||||||
|
devMode?: boolean,
|
||||||
|
dev: DevUiConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
restrictions?: RestrictionsSettings;
|
||||||
|
|
||||||
|
crop: {
|
||||||
|
default: any;
|
||||||
|
},
|
||||||
|
stretch: {
|
||||||
|
default: any;
|
||||||
|
conditionalDifferencePercent: number // black bars less than this wide will trigger stretch
|
||||||
|
// if mode is set to '1'. 1.0=100%
|
||||||
|
},
|
||||||
|
kbm: { // TODO: we dont use this anymore
|
||||||
|
enabled: boolean, // if keyboard/mouse handler service will run
|
||||||
|
keyboardEnabled: boolean, // if keyboard shortcuts are processed
|
||||||
|
mouseEnabled: boolean, // if mouse movement is processed
|
||||||
|
}
|
||||||
|
|
||||||
|
zoom: {
|
||||||
|
minLogZoom: number,
|
||||||
|
maxLogZoom: number,
|
||||||
|
announceDebounce: number // we wait this long before announcing new zoom
|
||||||
|
},
|
||||||
|
|
||||||
|
miscSettings: {
|
||||||
|
mousePan: {
|
||||||
|
enabled: boolean
|
||||||
|
},
|
||||||
|
mousePanReverseMouse: boolean,
|
||||||
|
defaultAr?: any
|
||||||
|
},
|
||||||
|
|
||||||
|
resizer: {
|
||||||
|
setStyleString: {
|
||||||
|
maxRetries: number,
|
||||||
|
retryTimeout: number
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pageInfo: {
|
||||||
|
timeouts: {
|
||||||
|
urlCheck: number,
|
||||||
|
rescan: number
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pan?: any,
|
||||||
|
version?: string,
|
||||||
|
preventReload?: boolean,
|
||||||
|
lastModified?: Date,
|
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
// ::: MITIGATIONS :::
|
||||||
|
// -----------------------------------------
|
||||||
|
// Settings for browser bug workarounds.
|
||||||
|
mitigations?: {
|
||||||
|
zoomLimit?: {
|
||||||
|
enabled?: boolean,
|
||||||
|
fullscreenOnly?: boolean,
|
||||||
|
limit?: number,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// This object fulfills the same purpose as 'actions', but is written in less retarded and overly
|
||||||
|
// complicated way. Hopefully it'll be easier to maintain it that way.
|
||||||
|
commands?: {
|
||||||
|
crop?: CommandInterface[],
|
||||||
|
stretch?: CommandInterface[],
|
||||||
|
zoom?: CommandInterface[],
|
||||||
|
pan?: CommandInterface[],
|
||||||
|
internal?: CommandInterface[],
|
||||||
|
},
|
||||||
|
whatsNewChecked: boolean,
|
||||||
|
newFeatureTracker: any,
|
||||||
|
// -----------------------------------------
|
||||||
|
// ::: SITE CONFIGURATION :::
|
||||||
|
// -----------------------------------------
|
||||||
|
// Config for a given page:
|
||||||
|
//
|
||||||
|
// <hostname> : {
|
||||||
|
// status: <option> // should extension work on this site?
|
||||||
|
// arStatus: <option> // should we do autodetection on this site?
|
||||||
|
//
|
||||||
|
// defaultAr?: <ratio> // automatically apply this aspect ratio on this side. Use extension defaults if undefined.
|
||||||
|
// stretch? <stretch mode> // automatically stretch video on this site in this manner
|
||||||
|
// videoAlignment? <left|center|right>
|
||||||
|
//
|
||||||
|
// type: <official|community|user> // 'official' — blessed by Tam.
|
||||||
|
// // 'community' — blessed by reddit.
|
||||||
|
// // 'user' — user-defined (not here)
|
||||||
|
// override: <true|false> // override user settings for this site on update
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Valid values for options:
|
||||||
|
//
|
||||||
|
// status, arStatus, statusEmbedded:
|
||||||
|
//
|
||||||
|
// * enabled — always allow, full
|
||||||
|
// * basic — allow, but only the basic version without playerData
|
||||||
|
// * default — allow if default is to allow, block if default is to block
|
||||||
|
// * disabled — never allow
|
||||||
|
//
|
||||||
|
sites: {
|
||||||
|
[x: string]: SiteSettingsInterface,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SiteSettingsInterface {
|
||||||
|
notes?: string; // any special things related to this site.
|
||||||
|
|
||||||
|
enable: ExtensionMode;
|
||||||
|
enableAard: ExtensionMode;
|
||||||
|
enableKeyboard: InputHandlingMode;
|
||||||
|
enableUI: ExtensionMode;
|
||||||
|
|
||||||
|
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy; // presumed to be 'Use as default' if not defined
|
||||||
|
overrideWhenEmbedded?: EmbeddedContentSettingsOverridePolicy;
|
||||||
|
|
||||||
|
|
||||||
|
autocreated?: boolean;
|
||||||
|
type?: SiteSupportLevel;
|
||||||
|
defaultType: SiteSupportLevel;
|
||||||
|
|
||||||
|
// must be defined in @global and @empty
|
||||||
|
persistCSA?: CropModePersistence, // CSA - crop, stretch, alignment
|
||||||
|
|
||||||
|
defaults?: { // must be defined in @global and @empty
|
||||||
|
crop?: {type: AspectRatioType, [x: string]: any},
|
||||||
|
stretch?: {type: StretchType, ratio?: number},
|
||||||
|
alignment?: {x: VideoAlignmentType, y: VideoAlignmentType},
|
||||||
|
}
|
||||||
|
|
||||||
|
cropModePersistence?: CropModePersistence;
|
||||||
|
stretchModePersistence?: CropModePersistence;
|
||||||
|
alignmentPersistence?: CropModePersistence;
|
||||||
|
|
||||||
|
playerAutoConfig?: PlayerAutoConfigInterface;
|
||||||
|
|
||||||
|
activeDOMConfig?: string;
|
||||||
|
DOMConfig?: { [x: string]: SiteDOMSettingsInterface };
|
||||||
|
|
||||||
|
// the following script are for extension caching and shouldn't be saved.
|
||||||
|
// if they _are_ saved, they will be overwritten
|
||||||
|
currentDOMConfig?: SiteDOMSettingsInterface;
|
||||||
|
|
||||||
|
// the following fields are for use with extension update script
|
||||||
|
override?: boolean; // whether settings for this site will be overwritten by extension upgrade script
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlayerAutoConfigInterface {
|
||||||
|
modified: boolean;
|
||||||
|
initialIndex: number;
|
||||||
|
currentIndex: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SiteDOMSettingsInterface {
|
||||||
|
type: SiteSupportLevel; // 'official' | 'community' | 'user-defined' | 'modified' | undefined;
|
||||||
|
elements?: {
|
||||||
|
player?: PlayerDOMSettingsInterface,
|
||||||
|
video?: PlayerDOMSettingsInterface,
|
||||||
|
};
|
||||||
|
customCss?: string;
|
||||||
|
periodicallyRefreshPlayerElement?: boolean;
|
||||||
|
|
||||||
|
// the following script are for extension caching and shouldn't be saved.
|
||||||
|
// if they _are_ saved, they will be overwritten
|
||||||
|
anchorElementIndex?: number;
|
||||||
|
anchorElement?: HTMLElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlayerDOMSettingsInterface {
|
||||||
|
detectionMode: PlayerDetectionMode,
|
||||||
|
allowAutoFallback: boolean,
|
||||||
|
ancestorIndex?: number,
|
||||||
|
querySelectors?: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SiteDOMElementSettingsInterface {
|
||||||
|
manual?: boolean;
|
||||||
|
querySelectors?: string;
|
||||||
|
index?: number; // previously: useRelativeAncestor + videoAncestor
|
||||||
|
mode?: 'index' | 'qs';
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SettingsInterface;
|
||||||
7
src/common/interfaces/StretchInterface.ts
Normal file
7
src/common/interfaces/StretchInterface.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import StretchType from '../enums/StretchType.enum';
|
||||||
|
|
||||||
|
export interface Stretch {
|
||||||
|
type: StretchType,
|
||||||
|
ratio?: number,
|
||||||
|
limit?: number,
|
||||||
|
}
|
||||||
33
src/common/js/ChromeShittinessMitigations.ts
Normal file
33
src/common/js/ChromeShittinessMitigations.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
/**
|
||||||
|
* For some reason, Chrome really doesn't like when chrome.runtime
|
||||||
|
* methods are wrapped inside a ES6 proxy object. If 'port' is a
|
||||||
|
* ES6 Proxy of a Port object that `chrome.runtime.connect()` creates,
|
||||||
|
* then Chrome will do bullshits like `port.sendMessage` and
|
||||||
|
* `port.onMessage.addListener` crashing your Vue3 UI with bullshits
|
||||||
|
* excuses, e.g.
|
||||||
|
*
|
||||||
|
* | TypeError: Illegal invocation. Function must be called on
|
||||||
|
* | an object of type Port
|
||||||
|
*
|
||||||
|
* which is some grade A bullshit because Firefox can handle that just
|
||||||
|
* fine.
|
||||||
|
*
|
||||||
|
* There's two ways how I could handle this:
|
||||||
|
* * Find out how to get the original object from the proxy Vue3
|
||||||
|
* creates, which would take time and ruin my xmass holiday, or
|
||||||
|
* * make a global object with a passive-aggressive name and ignore
|
||||||
|
* the very real possibility that there's prolly a reason Chrome
|
||||||
|
* does things in its own very special(tm) way, as if it had one
|
||||||
|
* extra chromosome over Firefox.
|
||||||
|
*
|
||||||
|
* Easy choice, really.
|
||||||
|
*/
|
||||||
|
export class ChromeShittinessMitigations {
|
||||||
|
static port = null;
|
||||||
|
|
||||||
|
static setProperty(property, value) {
|
||||||
|
ChromeShittinessMitigations[property] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ChromeShittinessMitigations;
|
||||||
24
src/common/js/IO.ts
Normal file
24
src/common/js/IO.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
class IO {
|
||||||
|
/**
|
||||||
|
* Export a (presumably json) string to file. Meant for use with content script.
|
||||||
|
* @param {*} jsonString string to be saved
|
||||||
|
*/
|
||||||
|
static async csStringToFile(jsonString) {
|
||||||
|
console.info("\n\n\n\n---------- Starting export of log to file ----------------");
|
||||||
|
|
||||||
|
console.info("[info] json string for exportObject:", jsonString.length);
|
||||||
|
|
||||||
|
const blob = new Blob([jsonString], {type: 'application/json'});
|
||||||
|
const fileUrl = URL.createObjectURL(blob);
|
||||||
|
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.style.display = 'none';
|
||||||
|
a.href = fileUrl;
|
||||||
|
a.download = 'ultrawidify-log.log';
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
window.URL.revokeObjectURL(fileUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default IO;
|
||||||
@ -1,6 +1,6 @@
|
|||||||
class KeyboardShortcutParser {
|
class KeyboardShortcutParser {
|
||||||
static parseShortcut(keypress) {
|
static parseShortcut(keypress) {
|
||||||
var shortcutCombo = '';
|
let shortcutCombo = '';
|
||||||
|
|
||||||
if (keypress.ctrlKey) {
|
if (keypress.ctrlKey) {
|
||||||
shortcutCombo += 'Ctrl + ';
|
shortcutCombo += 'Ctrl + ';
|
||||||
@ -21,6 +21,20 @@ class KeyboardShortcutParser {
|
|||||||
}
|
}
|
||||||
return shortcutCombo;
|
return shortcutCombo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static generateShortcutFromKeypress(event) {
|
||||||
|
return {
|
||||||
|
ctrlKey: event.ctrlKey,
|
||||||
|
altKey: event.altKey,
|
||||||
|
shiftKey: event.shiftKey,
|
||||||
|
metaKey: event.metaKey,
|
||||||
|
code: event.code,
|
||||||
|
key: event.key,
|
||||||
|
keyup: true,
|
||||||
|
keydown: false,
|
||||||
|
type: event.type, // only needed for purposes of EditShortcutButton
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default KeyboardShortcutParser;
|
export default KeyboardShortcutParser;
|
||||||
@ -1,17 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<p><i><a href="https://www.youtube.com/watch?v=Mn3YEJTSYs8&feature=youtu.be&t=770" target='_blank'>...</a> but as is normal, you can't use a free extensions without the developers begging for money. It's the bi-daily beggathon here on ... wherever here is.</i>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Jokes and references few will get aside, developing this extension does take a decent amount of time, motivation, carefully calibrated quantities
|
|
||||||
of alcohol and enough coffee to bankrupt a small nation.
|
|
||||||
</p>
|
|
||||||
<p>If you want to buy me a beer or bankroll my caffeine addiction, you can do so by <a href="https://paypal.me/tamius">clicking here</a>. All donations are appreciated.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
</script>
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
export default {
|
|
||||||
computed: {
|
|
||||||
scopeActions: function() {
|
|
||||||
return this.settings.active.actions.filter(x => x.scopes[this.scope] && x.scopes[this.scope].show) || [];
|
|
||||||
},
|
|
||||||
extensionActions: function(){
|
|
||||||
return this.scopeActions.filter(x => x.cmd.length === 1 && x.cmd[0].action === 'set-extension-mode') || [];
|
|
||||||
},
|
|
||||||
aardActions: function(){
|
|
||||||
return this.scopeActions.filter(x => x.cmd.length === 1 && x.cmd[0].action === 'set-autoar-mode') || [];
|
|
||||||
},
|
|
||||||
aspectRatioActions: function(){
|
|
||||||
return this.scopeActions.filter(x => x.cmd.length === 1 && x.cmd[0].action === 'set-ar') || [];
|
|
||||||
},
|
|
||||||
stretchActions: function(){
|
|
||||||
return this.scopeActions.filter(x => x.cmd.length === 1 && x.cmd[0].action === 'set-stretch') || [];
|
|
||||||
},
|
|
||||||
keyboardActions: function() {
|
|
||||||
return this.scopeActions.filter(x => x.cmd.length === 1 && x.cmd[0].action === 'set-keyboard') || [];
|
|
||||||
},
|
|
||||||
alignmentActions: function() {
|
|
||||||
return this.scopeActions.filter(x => x.cmd.length === 1 && x.cmd[0].action === 'set-alignment') || [];
|
|
||||||
},
|
|
||||||
otherActions: function() {
|
|
||||||
return this.scopeActions.filter(x => x.cmd.length > 1) || [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,10 +1,10 @@
|
|||||||
import Debug from '../conf/Debug';
|
import Debug from '../../ext/conf/Debug';
|
||||||
|
|
||||||
class ObjectCopy {
|
class ObjectCopy {
|
||||||
static addNew(current, newValues){
|
static addNew(current, newValues){
|
||||||
|
|
||||||
// clone target
|
// clone target
|
||||||
var out = JSON.parse(JSON.stringify(newValues));
|
let out = JSON.parse(JSON.stringify(newValues));
|
||||||
|
|
||||||
if(! current) {
|
if(! current) {
|
||||||
if(Debug.debug) {
|
if(Debug.debug) {
|
||||||
@ -14,7 +14,7 @@ class ObjectCopy {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
for(var k in out) {
|
for(let k in out) {
|
||||||
// if current key exist, replace it with existing value. Take no action otherwise.
|
// if current key exist, replace it with existing value. Take no action otherwise.
|
||||||
if(current[k]) {
|
if(current[k]) {
|
||||||
|
|
||||||
@ -37,7 +37,7 @@ class ObjectCopy {
|
|||||||
|
|
||||||
// add the values that would otherwise be deleted back to our object. (We need that so user-defined
|
// add the values that would otherwise be deleted back to our object. (We need that so user-defined
|
||||||
// sites don't get forgotten)
|
// sites don't get forgotten)
|
||||||
for(var k in current) {
|
for(let k in current) {
|
||||||
if (! out[k]) {
|
if (! out[k]) {
|
||||||
out[k] = current[k];
|
out[k] = current[k];
|
||||||
}
|
}
|
||||||
@ -47,7 +47,7 @@ class ObjectCopy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static overwrite(current, newValues){
|
static overwrite(current, newValues){
|
||||||
for(var k in newValues) {
|
for(let k in newValues) {
|
||||||
// if current key exist, replace it with existing value. Take no action otherwise.
|
// if current key exist, replace it with existing value. Take no action otherwise.
|
||||||
if (current[k] !== undefined) {
|
if (current[k] !== undefined) {
|
||||||
// Types and constructors of objects must match. If they don't, we always use the new value.
|
// Types and constructors of objects must match. If they don't, we always use the new value.
|
||||||
14
src/common/utils/_cp.ts
Normal file
14
src/common/utils/_cp.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* Creates deep copy of an object
|
||||||
|
* @param obj
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function _cp(obj) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(JSON.stringify(obj));
|
||||||
|
} catch (e) {
|
||||||
|
// console.error('Failed to parse json. This probably means that the data we received was not an object. Will return data as-is');
|
||||||
|
// console.error('data in:', obj, 'error:', e);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
}
|
||||||
12
src/common/utils/comparators.ts
Normal file
12
src/common/utils/comparators.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
export function collectionHas(collection, element): boolean {
|
||||||
|
for (let i = 0, len = collection.length; i < len; i++) {
|
||||||
|
if (collection[i] == element) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function equalish(a: number,b: number, tolerance: number): boolean {
|
||||||
|
return a > b - tolerance && a < b + tolerance;
|
||||||
|
}
|
||||||
103
src/common/utils/getElementStyles.ts
Normal file
103
src/common/utils/getElementStyles.ts
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
export type ProcessedElementStyles = {
|
||||||
|
[x: string]: {
|
||||||
|
css: string,
|
||||||
|
pxValue: number
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note that this function is written _very_ dangerously
|
||||||
|
* and includes absolutely no error handling
|
||||||
|
*/
|
||||||
|
function getPixelValue(value: string, element?: HTMLElement, prop?: string) {
|
||||||
|
if (value === undefined || value === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.endsWith('px')) {
|
||||||
|
// console.log('value ends in px:', value)
|
||||||
|
return parseFloat(value);
|
||||||
|
}
|
||||||
|
if (value.endsWith('%')) {
|
||||||
|
// console.log('value ends in %:', value)
|
||||||
|
// This allegedly doesn't work for certain types of properties, allegedly.
|
||||||
|
const parent = element?.parentElement;
|
||||||
|
if (parent && prop) {
|
||||||
|
const parentDimensions = parent.getBoundingClientRect();
|
||||||
|
return (parseFloat(value) * 0.01) * (prop.includes('height') || ['top', 'bottom'].includes(prop) ? parentDimensions.height : parentDimensions.width);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.endsWith("vw")) {
|
||||||
|
const viewportWidth = window.innerWidth;
|
||||||
|
return (parseFloat(value) / 100) * viewportWidth;
|
||||||
|
}
|
||||||
|
if (value.endsWith("vh")) {
|
||||||
|
const viewportHeight = window.innerHeight;
|
||||||
|
return (parseFloat(value) / 100) * viewportHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.endsWith("rem")) {
|
||||||
|
const rootFontSize = parseFloat(getComputedStyle(document.documentElement).fontSize);
|
||||||
|
return rootFontSize * parseFloat(value);
|
||||||
|
}
|
||||||
|
if (value.endsWith("em")) {
|
||||||
|
const fontSize = parseFloat(getComputedStyle(element).fontSize);
|
||||||
|
return fontSize * parseFloat(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function getElementStyles(element: HTMLElement, props: string[], theoryProps?: string[]): ProcessedElementStyles {
|
||||||
|
const stylesheets = document.styleSheets;
|
||||||
|
const computedStyles = getComputedStyle(element);
|
||||||
|
const stylesOut = {};
|
||||||
|
|
||||||
|
for (const stylesheet of stylesheets) {
|
||||||
|
// console.log('——————————————— processing stylesheet:', stylesheet);
|
||||||
|
try {
|
||||||
|
for (const rule of stylesheet?.cssRules) {
|
||||||
|
if (rule instanceof CSSStyleRule) {
|
||||||
|
if (element.matches(rule.selectorText)) {
|
||||||
|
// console.log('element matches rule:', rule);
|
||||||
|
|
||||||
|
for (const property in rule.style) {
|
||||||
|
if (!props.includes(property)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cssValue = rule.style.getPropertyValue(property);
|
||||||
|
|
||||||
|
if (theoryProps?.includes(property)) {
|
||||||
|
stylesOut[property] = {css: cssValue};
|
||||||
|
}
|
||||||
|
|
||||||
|
const actualValue = computedStyles.getPropertyValue(property);
|
||||||
|
|
||||||
|
const theory = getPixelValue(cssValue, element, property);
|
||||||
|
const practice = getPixelValue(actualValue, element, property);
|
||||||
|
|
||||||
|
if (theory && practice && (Math.abs(theory - practice) < 1)) {
|
||||||
|
stylesOut[property] = {
|
||||||
|
css: cssValue,
|
||||||
|
pxValue: theory
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
// Cross-origin styles amy cause problems
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return stylesOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
49
src/common/utils/getHost.ts
Normal file
49
src/common/utils/getHost.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
* Reqzests iframe parent hostname
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export async function getIframeParentHost(): Promise<string> {
|
||||||
|
return new Promise<any>((resolve) => {
|
||||||
|
let resolved = false;
|
||||||
|
let resendInterval;
|
||||||
|
|
||||||
|
function handleMessage(event) {
|
||||||
|
if (event.data.action === 'uw-parent-hostname') {
|
||||||
|
resolved = true;
|
||||||
|
clearInterval(resendInterval);
|
||||||
|
|
||||||
|
window.removeEventListener('message', handleMessage);
|
||||||
|
resolve(event.data.hostname);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('message', handleMessage);
|
||||||
|
resendInterval = setInterval(
|
||||||
|
() => {
|
||||||
|
if (!resolved) {
|
||||||
|
window.parent.postMessage(
|
||||||
|
{ action: 'uw-get-parent-hostname' },
|
||||||
|
'*'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
500
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function handleMessage(event) {
|
||||||
|
if (event.data.action === 'uw-get-parent-hostname') {
|
||||||
|
event.source.postMessage(
|
||||||
|
{action: 'uw-parent-hostname', hostname: window.location.hostname},
|
||||||
|
'*' as any
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setupHostnameReporting() {
|
||||||
|
window.removeEventListener('message', handleMessage); // setupHostnameReporting may run more than once
|
||||||
|
window.addEventListener('message', handleMessage);
|
||||||
|
}
|
||||||
3
src/common/utils/sleep.ts
Normal file
3
src/common/utils/sleep.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export async function sleep(timeout) {
|
||||||
|
return new Promise( (resolve, reject) => setTimeout(() => resolve(null), timeout));
|
||||||
|
}
|
||||||
145
src/ext/UWContent.ts
Normal file
145
src/ext/UWContent.ts
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
import Debug from '@src/ext/conf/Debug';
|
||||||
|
import CommsClient, { CommsOrigin } from '@src/ext/module/comms/CommsClient';
|
||||||
|
import EventBus from '@src/ext/module/EventBus';
|
||||||
|
import KeyboardHandler from '@src/ext/module/kbm/KeyboardHandler';
|
||||||
|
import { ComponentLogger } from '@src/ext/module/logging/ComponentLogger';
|
||||||
|
import { BLANK_LOGGER_CONFIG, LogAggregator } from '@src/ext/module/logging/LogAggregator';
|
||||||
|
import Settings from '@src/ext/module/settings/Settings';
|
||||||
|
import { SiteSettings } from '@src/ext/module/settings/SiteSettings';
|
||||||
|
import UI from '@src/ext/module/uwui/UI';
|
||||||
|
import PageInfo from '@src/ext/module/video-data/PageInfo';
|
||||||
|
import { getIframeParentHost, setupHostnameReporting } from '@src/common/utils/getHost';
|
||||||
|
|
||||||
|
export default class UWContent {
|
||||||
|
pageInfo: PageInfo;
|
||||||
|
comms: CommsClient;
|
||||||
|
settings: Settings;
|
||||||
|
siteSettings: SiteSettings;
|
||||||
|
keyboardHandler: KeyboardHandler;
|
||||||
|
logAggregator: LogAggregator;
|
||||||
|
logger: ComponentLogger;
|
||||||
|
eventBus: EventBus;
|
||||||
|
isIframe: boolean = false;
|
||||||
|
parentHostname: string;
|
||||||
|
|
||||||
|
globalUi: any;
|
||||||
|
|
||||||
|
commsHandlers: {
|
||||||
|
[x: string]: ((a: any, b?: any) => void | Promise<void>)[]
|
||||||
|
} = {
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(){
|
||||||
|
setupHostnameReporting();
|
||||||
|
this.isIframe = window.self !== window.top;
|
||||||
|
}
|
||||||
|
|
||||||
|
reloadSettings() {
|
||||||
|
try {
|
||||||
|
this.logger.debug('reloadSettings', 'Things happened in the popup. Will reload extension settings.');
|
||||||
|
this.init();
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Ultrawidify: settings reload failed. This probably shouldn\'t outright kill the extension, but page reload is recommended.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async init(){
|
||||||
|
if (this.isIframe) {
|
||||||
|
this.parentHostname = await getIframeParentHost();
|
||||||
|
console.warn('[uw-content] got iframe parent:', this.parentHostname);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (Debug.debug) {
|
||||||
|
console.log("[uw::main] loading configuration ...");
|
||||||
|
}
|
||||||
|
|
||||||
|
// logger init is the first thing that needs to run
|
||||||
|
try {
|
||||||
|
if (!this.logger) {
|
||||||
|
this.logAggregator = new LogAggregator('◈');
|
||||||
|
this.logger = new ComponentLogger(this.logAggregator, 'UWContent');
|
||||||
|
await this.logAggregator.init(BLANK_LOGGER_CONFIG);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("logger init failed!", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// init() is re-run any time settings change
|
||||||
|
if (this.comms) {
|
||||||
|
this.comms.destroy();
|
||||||
|
}
|
||||||
|
if (this.eventBus) {
|
||||||
|
this.eventBus.destroy();
|
||||||
|
}
|
||||||
|
if (!this.settings) {
|
||||||
|
this.settings = new Settings({
|
||||||
|
onSettingsChanged: () => this.reloadSettings(),
|
||||||
|
logAggregator: this.logAggregator
|
||||||
|
});
|
||||||
|
await this.settings.init();
|
||||||
|
this.siteSettings = this.settings.getSiteSettings({site: window.location.hostname, isIframe: this.isIframe, parentHostname: this.parentHostname});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.eventBus = new EventBus({name: 'content-script', commsOrigin: CommsOrigin.ContentScript});
|
||||||
|
this.eventBus.subscribe(
|
||||||
|
'uw-restart',
|
||||||
|
{
|
||||||
|
source: this,
|
||||||
|
function: () => this.initPhase2()
|
||||||
|
}
|
||||||
|
);
|
||||||
|
this.comms = new CommsClient('content-main-port', this.logAggregator, this.eventBus);
|
||||||
|
this.eventBus.setComms(this.comms);
|
||||||
|
|
||||||
|
|
||||||
|
this.initPhase2();
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Ultrawidify initialization failed for some reason:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// we always initialize extension, even if it's disabled.
|
||||||
|
initPhase2() {
|
||||||
|
try {
|
||||||
|
if (this.pageInfo) {
|
||||||
|
this.logger.info('setup', 'An instance of pageInfo already exists and will be destroyed.');
|
||||||
|
this.pageInfo.destroy();
|
||||||
|
}
|
||||||
|
this.pageInfo = new PageInfo(this.eventBus, this.siteSettings, this.settings, this.logAggregator);
|
||||||
|
this.logger.debug('setup', "pageInfo initialized.");
|
||||||
|
|
||||||
|
this.logger.debug('setup', "will try to initiate KeyboardHandler.");
|
||||||
|
|
||||||
|
if (this.keyboardHandler) {
|
||||||
|
this.keyboardHandler.destroy();
|
||||||
|
}
|
||||||
|
this.keyboardHandler = new KeyboardHandler(this.eventBus, this.siteSettings, this.settings, this.logAggregator);
|
||||||
|
this.keyboardHandler.init();
|
||||||
|
|
||||||
|
this.logger.debug('setup', "KeyboardHandler initiated.");
|
||||||
|
|
||||||
|
if (this.globalUi) {
|
||||||
|
this.globalUi.destroy();
|
||||||
|
}
|
||||||
|
// this.globalUi = new UI('ultrawidify-global-ui', {eventBus: this.eventBus, isGlobal: true});
|
||||||
|
// this.globalUi.enable();
|
||||||
|
// this.globalUi.setUiVisibility(false);
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Ultrawidify: failed to start extension. Error:', e)
|
||||||
|
this.logger.error('setup', "FAILED TO START EXTENSION. Error:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.eventBus.unsubscribeAll(this);
|
||||||
|
if (this.pageInfo) {
|
||||||
|
this.pageInfo.destroy();
|
||||||
|
}
|
||||||
|
if (this.keyboardHandler) {
|
||||||
|
this.keyboardHandler.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
437
src/ext/UWServer.ts
Normal file
437
src/ext/UWServer.ts
Normal file
@ -0,0 +1,437 @@
|
|||||||
|
import { Runtime } from 'chrome';
|
||||||
|
import EventBus from '@src/ext/module/EventBus';
|
||||||
|
import Settings from '@src/ext/module/settings/Settings';
|
||||||
|
import { ComponentLogger } from '@src/ext/module/logging/ComponentLogger';
|
||||||
|
import { BLANK_LOGGER_CONFIG, LogAggregator } from '@src/ext/module/logging/LogAggregator';
|
||||||
|
import CommsServer from '@src/ext/module/comms/CommsServer';
|
||||||
|
import BrowserDetect from '@src/ext/conf/BrowserDetect';
|
||||||
|
import { HostInfo } from '@src/common/interfaces/HostData.interface';
|
||||||
|
import { ExtensionEnvironment } from '@src/common/interfaces/SettingsInterface';
|
||||||
|
import { CommsOrigin } from '@src/ext/module/comms/CommsClient';
|
||||||
|
|
||||||
|
|
||||||
|
const BASE_LOGGING_STYLES = {
|
||||||
|
'log': 'background-color: #243; color: #4a8',
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class UWServer {
|
||||||
|
settings: Settings;
|
||||||
|
logger: ComponentLogger;
|
||||||
|
logAggregator: LogAggregator;
|
||||||
|
comms: CommsServer;
|
||||||
|
eventBus: EventBus;
|
||||||
|
|
||||||
|
ports: any[] = [];
|
||||||
|
hasVideos: boolean;
|
||||||
|
currentSite: string = '';
|
||||||
|
videoTabs: any = {};
|
||||||
|
currentTabId: number = 0;
|
||||||
|
|
||||||
|
selectedSubitem: any = {
|
||||||
|
'siteSettings': undefined,
|
||||||
|
'videoSettings': undefined,
|
||||||
|
}
|
||||||
|
|
||||||
|
eventBusCommands = {
|
||||||
|
'popup-set-selected-tab': {
|
||||||
|
function: (message) => this.setSelectedTab(message.selectedMenu, message.selectedSubitem)
|
||||||
|
},
|
||||||
|
'has-video': {
|
||||||
|
function: (message, context) => this.registerVideo(context.comms.sender)
|
||||||
|
},
|
||||||
|
'noVideo' : {
|
||||||
|
function: (message, context) => this.unregisterVideo(context.comms.sender)
|
||||||
|
},
|
||||||
|
'inject-css': {
|
||||||
|
function: (message, context) => this.injectCss(message.cssString, context.comms.sender)
|
||||||
|
},
|
||||||
|
'eject-css': {
|
||||||
|
function: (message, context) => this.removeCss(message.cssString, context.comms.sender)
|
||||||
|
},
|
||||||
|
'replace-css': {
|
||||||
|
function: (message, context) => this.replaceCss(message.oldCssString, message.newCssString, context.comms.sender)
|
||||||
|
},
|
||||||
|
'get-current-site': {
|
||||||
|
function: (message, context) => this.getCurrentSite(context.comms.sender)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private gcTimeout: any;
|
||||||
|
uiLoggerInitialized: boolean = false;
|
||||||
|
|
||||||
|
|
||||||
|
//#region getters
|
||||||
|
get activeTab() {
|
||||||
|
return chrome.tabs.query({currentWindow: true, active: true});
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
async setup() {
|
||||||
|
try {
|
||||||
|
// logger is the first thing that goes up
|
||||||
|
const loggingOptions = BLANK_LOGGER_CONFIG;
|
||||||
|
|
||||||
|
this.logAggregator = new LogAggregator('🔶bg-script🔶');
|
||||||
|
this.logger = new ComponentLogger(this.logAggregator, 'UwServer', {styles: BASE_LOGGING_STYLES});
|
||||||
|
await this.logAggregator.init(loggingOptions);
|
||||||
|
|
||||||
|
this.settings = new Settings({logAggregator: this.logAggregator});
|
||||||
|
await this.settings.init();
|
||||||
|
|
||||||
|
this.eventBus = new EventBus({isUWServer: true, commsOrigin: CommsOrigin.Server});
|
||||||
|
|
||||||
|
this.eventBus.subscribeMulti(this.eventBusCommands, this);
|
||||||
|
|
||||||
|
this.comms = new CommsServer(this);
|
||||||
|
this.eventBus.setComms(this.comms);
|
||||||
|
|
||||||
|
chrome.tabs.onActivated.addListener((m) => {this.onTabSwitched(m)});
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Ultrawidify [server]: failed to start. Reason:`, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async _promisifyTabsGet(browserObj, tabId){
|
||||||
|
return new Promise( (resolve, reject) => {
|
||||||
|
browserObj.tabs.get(tabId, (tab) => resolve(tab));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//#region CSS management
|
||||||
|
|
||||||
|
async injectCss(css, sender) {
|
||||||
|
if (!sender?.tab?.id) {
|
||||||
|
// console.warn('invalid injectCss received!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.info('injectCss', 'Trying to inject CSS into tab', sender.tab.id, ', frameId:', sender.frameId, 'css:\n', css)
|
||||||
|
if (!css) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await chrome.scripting.insertCSS({
|
||||||
|
target: {
|
||||||
|
tabId: sender.tab.id,
|
||||||
|
frameIds: [
|
||||||
|
sender.frameId
|
||||||
|
]
|
||||||
|
},
|
||||||
|
css,
|
||||||
|
origin: "USER"
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
this.logger.error('injectCss', 'Error while injecting css:', {error: e, css, sender});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async removeCss(css, sender) {
|
||||||
|
if (!sender?.tab) {
|
||||||
|
// console.warn('invalid removeCss received!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await chrome.scripting.removeCSS({
|
||||||
|
target: {
|
||||||
|
tabId: sender.tab.id,
|
||||||
|
frameIds: [
|
||||||
|
sender.frameId
|
||||||
|
]
|
||||||
|
},
|
||||||
|
css,
|
||||||
|
origin: "USER"
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
this.logger.error('injectCss', 'Error while removing css:', {error: e, css, sender});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async replaceCss(oldCss, newCss, sender) {
|
||||||
|
if (!sender?.tab) {
|
||||||
|
// console.warn('invalid replaceCss received!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (oldCss !== newCss) {
|
||||||
|
this.removeCss(oldCss, sender);
|
||||||
|
this.injectCss(newCss, sender);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
extractHostname(url){
|
||||||
|
var hostname;
|
||||||
|
|
||||||
|
if (!url) {
|
||||||
|
return "<no url>";
|
||||||
|
}
|
||||||
|
|
||||||
|
// extract hostname
|
||||||
|
if (url.indexOf("://") > -1) { //find & remove protocol (http, ftp, etc.) and get hostname
|
||||||
|
hostname = url.split('/')[2];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
hostname = url.split('/')[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
hostname = hostname.split(':')[0]; //find & remove port number
|
||||||
|
hostname = hostname.split('?')[0]; //find & remove "?"
|
||||||
|
|
||||||
|
return hostname;
|
||||||
|
}
|
||||||
|
|
||||||
|
async onTabSwitched(activeInfo){
|
||||||
|
this.hasVideos = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.currentTabId = activeInfo.tabId; // just for readability
|
||||||
|
|
||||||
|
let tab;
|
||||||
|
if (BrowserDetect.firefox) {
|
||||||
|
tab = await chrome.tabs.get(this.currentTabId);
|
||||||
|
} else if (BrowserDetect.anyChromium) {
|
||||||
|
tab = await this._promisifyTabsGet(chrome, this.currentTabId);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentSite = this.extractHostname(tab.url);
|
||||||
|
this.logger.info('onTabSwitched', 'user switched tab. New site:', this.currentSite);
|
||||||
|
} catch(e) {
|
||||||
|
this.logger.info('onTabSwitched', 'there was a problem getting current site:', e)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.selectedSubitem = {
|
||||||
|
'siteSettings': undefined,
|
||||||
|
'videoSettings': undefined,
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: change extension icon based on whether there's any videos on current page
|
||||||
|
}
|
||||||
|
|
||||||
|
registerVideo(sender) {
|
||||||
|
if (!sender?.tab?.url) {
|
||||||
|
// console.warn('invalid registerVideo received!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.logger.info('registerVideo', 'Registering video.\nsender:', sender);
|
||||||
|
|
||||||
|
const tabHostname = this.extractHostname(sender.tab.url);
|
||||||
|
const frameHostname = this.extractHostname(sender.url);
|
||||||
|
|
||||||
|
// check for orphaned/outdated values and remove them if necessary
|
||||||
|
if (this.videoTabs[sender.tab.id]?.host != tabHostname) {
|
||||||
|
delete this.videoTabs[sender.tab.id]
|
||||||
|
} else if(this.videoTabs[sender.tab.id]?.frames[sender.frameId]?.host != frameHostname) {
|
||||||
|
delete this.videoTabs[sender.tab.id].frames[sender.frameId];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.videoTabs[sender.tab.id]) {
|
||||||
|
this.videoTabs[sender.tab.id].frames[sender.frameId] = {
|
||||||
|
id: sender.frameId,
|
||||||
|
host: frameHostname,
|
||||||
|
url: sender.url,
|
||||||
|
registerTime: Date.now(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.videoTabs[sender.tab.id] = {
|
||||||
|
id: sender.tab.id,
|
||||||
|
host: tabHostname,
|
||||||
|
url: sender.tab.url,
|
||||||
|
frames: {}
|
||||||
|
};
|
||||||
|
this.videoTabs[sender.tab.id].frames[sender.frameId] = {
|
||||||
|
id: sender.frameId,
|
||||||
|
host: frameHostname,
|
||||||
|
url: sender.url,
|
||||||
|
registerTime: Date.now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.info('registerVideo', 'Video registered. current videoTabs:', this.videoTabs);
|
||||||
|
}
|
||||||
|
|
||||||
|
unregisterVideo(sender) {
|
||||||
|
if (!sender?.tab) {
|
||||||
|
// console.warn('invalid unregisterVideo received!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.info('unregisterVideo', 'Unregistering video.\nsender:', sender);
|
||||||
|
if (this.videoTabs[sender.tab.id]) {
|
||||||
|
if ( Object.keys(this.videoTabs[sender.tab.id].frames).length <= 1) {
|
||||||
|
delete this.videoTabs[sender.tab.id]
|
||||||
|
} else {
|
||||||
|
if(this.videoTabs[sender.tab.id].frames[sender.frameId]) {
|
||||||
|
delete this.videoTabs[sender.tab.id].frames[sender.frameId];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.logger.info('unregisterVideo', 'Video has been unregistered. Current videoTabs:', this.videoTabs);
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelectedTab(menu, subitem) {
|
||||||
|
this.logger.info('setSelectedTab', 'saving selected tab for', menu, ':', subitem);
|
||||||
|
this.selectedSubitem[menu] = subitem;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCurrentSite(sender: Runtime.MessageSender) {
|
||||||
|
// if (!sender?.tab) {
|
||||||
|
// console.warn('invalid unregisterVideo received!');
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
const site = await this.getVideoTab();
|
||||||
|
|
||||||
|
// Don't propagate 'INVALID SITE' to the popup.
|
||||||
|
if (site.host === 'INVALID SITE') {
|
||||||
|
this.logger.info('getCurrentSite', 'Host is not valid — no info for current tab.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tabHostname = await this.getCurrentTabHostname();
|
||||||
|
this.logger.info('getCurrentSite', 'Returning data:', {site, tabHostname});
|
||||||
|
console.info('get-current-site : returning data:', {site, tabHostname});
|
||||||
|
|
||||||
|
this.eventBus.send(
|
||||||
|
'set-current-site',
|
||||||
|
{
|
||||||
|
site,
|
||||||
|
tabHostname,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
comms: {
|
||||||
|
forwardTo: 'popup'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCurrentTab() {
|
||||||
|
return (await chrome.tabs.query({active: true, currentWindow: true}))[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private populateFrameVideoStatus(tabId: number, hostnames: string[]) {
|
||||||
|
const out: HostInfo[] = [];
|
||||||
|
|
||||||
|
for (const host of hostnames) {
|
||||||
|
let video = {hasVideo: false, minEnvironment: ExtensionEnvironment.Normal, maxEnvironment: ExtensionEnvironment.Fullscreen};
|
||||||
|
|
||||||
|
for (const frameKey in this.videoTabs[tabId].frames) {
|
||||||
|
const frame = this.videoTabs[tabId].frames[frameKey];
|
||||||
|
|
||||||
|
if (frame.host === host) {
|
||||||
|
video.hasVideo = true;
|
||||||
|
if (frame.environment > video.maxEnvironment) {
|
||||||
|
video.maxEnvironment = frame.environment;
|
||||||
|
}
|
||||||
|
if (frame.environment < video.minEnvironment) {
|
||||||
|
video.minEnvironment = frame.environment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out.push({
|
||||||
|
host,
|
||||||
|
...video,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _lastVideoTabData: any | undefined;
|
||||||
|
async getVideoTab() {
|
||||||
|
// friendly reminder: if current tab doesn't have a video,
|
||||||
|
// there won't be anything in this.videoTabs[this.currentTabId]
|
||||||
|
|
||||||
|
const ctab = await this.getCurrentTab();
|
||||||
|
|
||||||
|
if (!ctab || !ctab.id) {
|
||||||
|
return {
|
||||||
|
host: 'INVALID SITE',
|
||||||
|
frames: [],
|
||||||
|
hostnames: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const hostnames = await this.comms.listUniqueFrameHosts();
|
||||||
|
|
||||||
|
// this probably means we're inside a problematic page
|
||||||
|
if (!this.videoTabs[ctab.id]) {
|
||||||
|
return this._lastVideoTabData ?? {
|
||||||
|
host: 'INVALID SITE',
|
||||||
|
frames: [],
|
||||||
|
hostnames: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if video is older than PageInfo's video rescan period (+ 4000ms of grace),
|
||||||
|
// we clean it up from videoTabs[tabId].frames array.
|
||||||
|
const ageLimit = Date.now() - this.settings.active.pageInfo.timeouts.rescan - 4000;
|
||||||
|
try {
|
||||||
|
for (const key in this.videoTabs[ctab.id].frames) {
|
||||||
|
if (this.videoTabs[ctab.id].frames[key].registerTime < ageLimit) {
|
||||||
|
delete this.videoTabs[ctab.id].frames[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// something went wrong. There's prolly no frames.
|
||||||
|
// this probably shouldn't ever run.
|
||||||
|
return {
|
||||||
|
host: this.extractHostname(ctab.url),
|
||||||
|
hostnames: [],
|
||||||
|
frames: [],
|
||||||
|
selected: this.selectedSubitem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure hostnames with videos come before hostnames without videos
|
||||||
|
// Also ensure hostnames are sorted alphabetically
|
||||||
|
const populatedHostnames = this.populateFrameVideoStatus(ctab.id, hostnames);
|
||||||
|
populatedHostnames.sort((a: HostInfo, b: HostInfo) => {
|
||||||
|
return a.hasVideo === b.hasVideo
|
||||||
|
? a.host === b.host ? 0 : a.host < b.host ? -1 : 1
|
||||||
|
: a.hasVideo < b.hasVideo ? 1 : -1;
|
||||||
|
});
|
||||||
|
|
||||||
|
this._lastVideoTabData = {
|
||||||
|
host: this.extractHostname(ctab.url),
|
||||||
|
hostnames: populatedHostnames.map(x => x.host), // todo: try eliminating this
|
||||||
|
populatedHostnames: populatedHostnames,
|
||||||
|
selected: this.selectedSubitem
|
||||||
|
};
|
||||||
|
|
||||||
|
return this._lastVideoTabData;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCurrentTabHostname() {
|
||||||
|
const activeTab = await this.activeTab;
|
||||||
|
|
||||||
|
if (!activeTab || activeTab.length < 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = activeTab[0].url;
|
||||||
|
|
||||||
|
if (!url) {
|
||||||
|
console.log('no URL for active tab:', activeTab[0].url);
|
||||||
|
}
|
||||||
|
|
||||||
|
var hostname;
|
||||||
|
|
||||||
|
if (url.indexOf("://") > -1) { //find & remove protocol (http, ftp, etc.) and get hostname
|
||||||
|
hostname = url.split('/')[2];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
hostname = url.split('/')[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
hostname = hostname.split(':')[0]; //find & remove port number
|
||||||
|
hostname = hostname.split('?')[0]; //find & remove "?"
|
||||||
|
|
||||||
|
return hostname;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,28 +1,37 @@
|
|||||||
import VideoAlignment from '../../common/enums/video-alignment.enum';
|
import VideoAlignmentType from '../../common/enums/VideoAlignmentType.enum';
|
||||||
import Stretch from '../../common/enums/stretch.enum';
|
import StretchType from '../../common/enums/StretchType.enum';
|
||||||
import ExtensionMode from '../../common/enums/extension-mode.enum';
|
import ExtensionMode from '../../common/enums/ExtensionMode.enum';
|
||||||
import AspectRatio from '../../common/enums/aspect-ratio.enum';
|
import AspectRatioType from '../../common/enums/AspectRatioType.enum';
|
||||||
|
import CropModePersistence from '../../common/enums/CropModePersistence.enum';
|
||||||
|
|
||||||
var ActionList = {
|
var ActionList = {
|
||||||
'set-ar': {
|
'set-ar': {
|
||||||
name: 'Set aspect ratio',
|
name: 'Set aspect ratio',
|
||||||
args: [{
|
args: [{
|
||||||
name: 'Automatic',
|
name: 'Automatic',
|
||||||
arg: AspectRatio.Automatic,
|
arg: AspectRatioType.Automatic,
|
||||||
},{
|
},{
|
||||||
name: 'Fit width',
|
name: 'Fit width',
|
||||||
arg: AspectRatio.FitWidth,
|
arg: AspectRatioType.FitWidth,
|
||||||
},{
|
},{
|
||||||
name: 'Fit height',
|
name: 'Fit height',
|
||||||
arg: AspectRatio.FitHeight,
|
arg: AspectRatioType.FitHeight,
|
||||||
},{
|
},{
|
||||||
name: 'Reset',
|
name: 'Reset',
|
||||||
arg: AspectRatio.Reset,
|
arg: AspectRatioType.Reset,
|
||||||
},{
|
},{
|
||||||
name: 'Manually specify ratio',
|
name: 'Manually specify ratio',
|
||||||
arg: AspectRatio.Fixed,
|
arg: AspectRatioType.Fixed,
|
||||||
customArg: true,
|
customArg: true,
|
||||||
hintHTML: '',
|
customSetter: (value) => {
|
||||||
|
const [width, height] = value.split(':');
|
||||||
|
|
||||||
|
if (width && height) {
|
||||||
|
return +width / +height;
|
||||||
|
}
|
||||||
|
return +width;
|
||||||
|
},
|
||||||
|
hintHTML: '<small>Enter the aspect ratio as {width}:{height} or a single number, e.g. "21:9", "2.35:1", or "2.35" (without quotes).</small>',
|
||||||
}],
|
}],
|
||||||
scopes: {
|
scopes: {
|
||||||
global: false,
|
global: false,
|
||||||
@ -30,23 +39,64 @@ var ActionList = {
|
|||||||
page: true,
|
page: true,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
'set-ar-persistence': {
|
||||||
|
name: 'Set crop mode persistence',
|
||||||
|
args: [{
|
||||||
|
name: 'Never persist',
|
||||||
|
arg: CropModePersistence.Disabled,
|
||||||
|
},{
|
||||||
|
name: 'While on page',
|
||||||
|
arg: CropModePersistence.UntilPageReload,
|
||||||
|
},{
|
||||||
|
name: 'Current session',
|
||||||
|
arg: CropModePersistence.CurrentSession,
|
||||||
|
},{
|
||||||
|
name: 'Always persist',
|
||||||
|
arg: CropModePersistence.Forever,
|
||||||
|
}, {
|
||||||
|
name: 'Default',
|
||||||
|
arg: CropModePersistence.Default,
|
||||||
|
scopes: {
|
||||||
|
site: true,
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
scopes: {
|
||||||
|
global: true,
|
||||||
|
site: true,
|
||||||
|
page: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
'set-stretch': {
|
'set-stretch': {
|
||||||
name: 'Set stretch',
|
name: 'Set stretch',
|
||||||
args: [{
|
args: [{
|
||||||
name: 'Normal',
|
name: 'Normal',
|
||||||
arg: Stretch.NoStretch
|
arg: StretchType.NoStretch
|
||||||
},{
|
},{
|
||||||
name: 'Basic',
|
name: 'Basic',
|
||||||
arg: Stretch.Basic,
|
arg: StretchType.Basic,
|
||||||
},{
|
},{
|
||||||
name: 'Hybrid',
|
name: 'Hybrid',
|
||||||
arg: Stretch.Hybrid,
|
arg: StretchType.Hybrid,
|
||||||
},{
|
},{
|
||||||
name: 'Thin borders',
|
name: 'Thin borders',
|
||||||
arg: Stretch.Conditional,
|
arg: StretchType.Conditional,
|
||||||
|
},{
|
||||||
|
name: 'Fixed (source)',
|
||||||
|
arg: StretchType.FixedSource,
|
||||||
|
customArg: true,
|
||||||
|
scopes: {
|
||||||
|
page: true,
|
||||||
|
}
|
||||||
|
},{
|
||||||
|
name: 'Fixed (displayed)',
|
||||||
|
arg: StretchType.Fixed,
|
||||||
|
customArg: true,
|
||||||
|
scopes: {
|
||||||
|
page: true,
|
||||||
|
}
|
||||||
},{
|
},{
|
||||||
name: 'Default',
|
name: 'Default',
|
||||||
arg: Stretch.Default,
|
arg: StretchType.Default,
|
||||||
scopes: {
|
scopes: {
|
||||||
site: true
|
site: true
|
||||||
}
|
}
|
||||||
@ -61,16 +111,16 @@ var ActionList = {
|
|||||||
name: 'Set video alignment',
|
name: 'Set video alignment',
|
||||||
args: [{
|
args: [{
|
||||||
name: 'Left',
|
name: 'Left',
|
||||||
arg: VideoAlignment.Left,
|
arg: VideoAlignmentType.Left,
|
||||||
},{
|
},{
|
||||||
name: 'Center',
|
name: 'Center',
|
||||||
arg: VideoAlignment.Center,
|
arg: VideoAlignmentType.Center,
|
||||||
},{
|
},{
|
||||||
name: 'Right',
|
name: 'Right',
|
||||||
arg: VideoAlignment.Right
|
arg: VideoAlignmentType.Right
|
||||||
},{
|
},{
|
||||||
name: 'Default',
|
name: 'Default',
|
||||||
arg: VideoAlignment.Default,
|
arg: VideoAlignmentType.Default,
|
||||||
scopes: {
|
scopes: {
|
||||||
site: true,
|
site: true,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,44 @@
|
|||||||
import Debug from './Debug.js';
|
import browser from "vuex-webextensions/dist/browser";
|
||||||
|
|
||||||
|
if (process.env.CHANNEL !== 'stable') {
|
||||||
|
console.info('Loaded BrowserDetect');
|
||||||
|
}
|
||||||
|
|
||||||
|
function detectEdgeUA() {
|
||||||
|
try {
|
||||||
|
return /Edg\/(\.?[0-9]*)*$/.test(window.navigator.userAgent);
|
||||||
|
} catch {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBrowserObj() {
|
||||||
|
return process.env.BROWSER === 'firefox' ? browser : chrome;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRuntime() {
|
||||||
|
return process.env.BROWSER === 'firefox' ? browser.runtime : chrome.runtime;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getURL(url) {
|
||||||
|
return process.env.BROWSER === 'firefox' ? browser.runtime.getURL(url) : chrome.runtime.getURL(url);
|
||||||
|
}
|
||||||
|
|
||||||
const BrowserDetect = {
|
const BrowserDetect = {
|
||||||
firefox: process.env.BROWSER === 'firefox',
|
firefox: process.env.BROWSER === 'firefox',
|
||||||
|
anyChromium: process.env.BROWSER !== 'firefox',
|
||||||
chrome: process.env.BROWSER === 'chrome',
|
chrome: process.env.BROWSER === 'chrome',
|
||||||
edge: process.env.BROWSER === 'edge',
|
edge: process.env.BROWSER === 'edge',
|
||||||
|
processEnvBrowser: process.env.BROWSER,
|
||||||
|
processEnvChannel: process.env.CHANNEL,
|
||||||
|
isEdgeUA: detectEdgeUA(),
|
||||||
|
browserObj: getBrowserObj(),
|
||||||
|
runtime: getRuntime(),
|
||||||
|
getURL: (url) => getURL(url),
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Debug.debug) {
|
if (process.env.CHANNEL !== 'stable') {
|
||||||
console.log("Loading: BrowserDetect.js\n\nprocess.env.BROWSER:", process.env.BROWSER, "Exporting BrowserDetect:", BrowserDetect);
|
console.info("BrowserDetect loaded:\n\nprocess.env.BROWSER:", process.env.BROWSER, "\nExporting BrowserDetect:", BrowserDetect);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default BrowserDetect;
|
export default BrowserDetect;
|
||||||
@ -1,3 +1,8 @@
|
|||||||
|
if (process.env.CHANNEL !== 'stable') {
|
||||||
|
console.info('We are not on stable channel. File init will be printed to console.');
|
||||||
|
console.info('Loading Debug.js');
|
||||||
|
}
|
||||||
|
|
||||||
// Set prod to true when releasing
|
// Set prod to true when releasing
|
||||||
// const _prod = true;
|
// const _prod = true;
|
||||||
const _prod = false;
|
const _prod = false;
|
||||||
@ -49,9 +54,13 @@ function __disableAllDebug(obj) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(Debug.debug)
|
if (Debug.debug) {
|
||||||
console.log("Guess we're debugging ultrawidify then. Debug.js must always load first, and others must follow.\nLoading: Debug.js");
|
console.info("Guess we're debugging ultrawidify then. Debug.js must always load first, and others must follow.\nLoading: Debug.js");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.env.CHANNEL !== 'stable') {
|
||||||
|
console.info('Loaded Debug.js');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export default Debug;
|
export default Debug;
|
||||||
@ -1,123 +0,0 @@
|
|||||||
// How to use:
|
|
||||||
// version: {ExtensionConf object, but only properties that get overwritten}
|
|
||||||
|
|
||||||
const ExtensionConfPatch = [
|
|
||||||
{
|
|
||||||
forVersion: '4.2.0',
|
|
||||||
sites: {
|
|
||||||
"old.reddit.com" : {
|
|
||||||
type: 'testing',
|
|
||||||
DOM: {
|
|
||||||
player: {
|
|
||||||
manual: true,
|
|
||||||
useRelativeAncestor: false,
|
|
||||||
querySelectors: '.reddit-video-player-root, .media-preview-content'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
css: '',
|
|
||||||
},
|
|
||||||
"www.reddit.com" : {
|
|
||||||
type: 'testing',
|
|
||||||
DOM: {
|
|
||||||
player: {
|
|
||||||
manual: true,
|
|
||||||
useRelativeAncestor: false,
|
|
||||||
querySelectors: '.reddit-video-player-root, .media-preview-content'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
css: '',
|
|
||||||
},
|
|
||||||
"www.youtube.com" : {
|
|
||||||
DOM: {
|
|
||||||
player: {
|
|
||||||
manual: true,
|
|
||||||
querySelectors: "#movie_player, #player",
|
|
||||||
additionalCss: "",
|
|
||||||
useRelativeAncestor: false,
|
|
||||||
playerNodeCss: "",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
forVersion: '4.2.3.1',
|
|
||||||
sites: {
|
|
||||||
"old.reddit.com" : {
|
|
||||||
type: 'testing',
|
|
||||||
DOM: {
|
|
||||||
player: {
|
|
||||||
manual: true,
|
|
||||||
useRelativeAncestor: false,
|
|
||||||
querySelectors: '.media-preview-content, .reddit-video-player-root'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
css: '',
|
|
||||||
},
|
|
||||||
"www.reddit.com" : {
|
|
||||||
type: 'testing',
|
|
||||||
DOM: {
|
|
||||||
player: {
|
|
||||||
manual: true,
|
|
||||||
useRelativeAncestor: false,
|
|
||||||
querySelectors: '.media-preview-content, .reddit-video-player-root'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
css: '',
|
|
||||||
},
|
|
||||||
"www.youtube.com" : {
|
|
||||||
DOM: {
|
|
||||||
player: {
|
|
||||||
manual: true,
|
|
||||||
querySelectors: "#movie_player, #player",
|
|
||||||
additionalCss: "",
|
|
||||||
useRelativeAncestor: false,
|
|
||||||
playerNodeCss: "",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
forVersion: '4.3.0',
|
|
||||||
sites: {
|
|
||||||
"old.reddit.com" : {
|
|
||||||
type: 'testing',
|
|
||||||
DOM: {
|
|
||||||
player: {
|
|
||||||
manual: false,
|
|
||||||
useRelativeAncestor: false,
|
|
||||||
querySelectors: '.reddit-video-player-root, .media-preview-content'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
css: 'video {\n width: 100% !important;\n height: 100% !important;\n}',
|
|
||||||
},
|
|
||||||
"www.reddit.com" : {
|
|
||||||
type: 'testing',
|
|
||||||
DOM: {
|
|
||||||
player: {
|
|
||||||
manual: false,
|
|
||||||
useRelativeAncestor: false,
|
|
||||||
querySelectors: '.reddit-video-player-root, .media-preview-content'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
css: 'video {\n width: 100% !important;\n height: 100% !important;\n}',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
forVersion: '4.3.1.1',
|
|
||||||
sites: {
|
|
||||||
'www.twitch.tv': {
|
|
||||||
DOM: {
|
|
||||||
player: {
|
|
||||||
manual: false,
|
|
||||||
querySelectors: "",
|
|
||||||
additionalCss: "",
|
|
||||||
useRelativeAncestor: false,
|
|
||||||
playerNodeCss: ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
export default ExtensionConfPatch;
|
|
||||||
539
src/ext/conf/ExtConfPatches.ts
Normal file
539
src/ext/conf/ExtConfPatches.ts
Normal file
@ -0,0 +1,539 @@
|
|||||||
|
// How to use:
|
||||||
|
// version: {ExtensionConf object, but only properties that get overwritten}
|
||||||
|
import AspectRatioType from '@src/common/enums/AspectRatioType.enum';
|
||||||
|
import CropModePersistence from '@src/common/enums/CropModePersistence.enum';
|
||||||
|
import EmbeddedContentSettingsOverridePolicy from '@src/common/enums/EmbeddedContentSettingsOverridePolicy.enum';
|
||||||
|
import ExtensionMode from '@src/common/enums/ExtensionMode.enum';
|
||||||
|
import { InputHandlingMode } from '@src/common/enums/InputHandlingMode.enum';
|
||||||
|
import LegacyExtensionMode from '@src/common/enums/LegacyExtensionMode.enum';
|
||||||
|
import { PlayerDetectionMode } from '@src/common/enums/PlayerDetectionMode.enum';
|
||||||
|
import { SiteSupportLevel } from '@src/common/enums/SiteSupportLevel.enum';
|
||||||
|
import SettingsInterface from '@src/common/interfaces/SettingsInterface';
|
||||||
|
import { _cp } from '@src/common/utils/_cp';
|
||||||
|
import { update } from 'lodash';
|
||||||
|
|
||||||
|
|
||||||
|
const ExtensionConfPatch = Object.freeze([
|
||||||
|
{
|
||||||
|
forVersion: '6.2.4',
|
||||||
|
updateFn: (userOptions: any, defaultOptions, logger?) => {
|
||||||
|
for (const site in userOptions.sites) {
|
||||||
|
(userOptions as any).sites[site].enableUI = {
|
||||||
|
fullscreen: LegacyExtensionMode.Default,
|
||||||
|
theater: LegacyExtensionMode.Default,
|
||||||
|
normal: LegacyExtensionMode.Default,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
userOptions.sites['@global'].enableUI = {
|
||||||
|
fullscreen: userOptions.ui.inPlayer.enabled ? LegacyExtensionMode.Enabled : LegacyExtensionMode.Disabled,
|
||||||
|
theater: LegacyExtensionMode.Enabled,
|
||||||
|
normal: (userOptions.ui.inPlayer.enabled && !userOptions.ui.inPlayer.enabledFullscreenOnly) ? LegacyExtensionMode.Enabled : LegacyExtensionMode.Disabled
|
||||||
|
}
|
||||||
|
userOptions.sites['@empty'].enableUI = {
|
||||||
|
fullscreen: LegacyExtensionMode.Default,
|
||||||
|
theater: LegacyExtensionMode.Default,
|
||||||
|
normal: LegacyExtensionMode.Default,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
forVersion: '6.2.6',
|
||||||
|
updateFn: (userOptions: any, defaultOptions, logger?) => {
|
||||||
|
console.log('[ultrawidify] Migrating settings — applying patches for version 6.2.6');
|
||||||
|
|
||||||
|
if (!userOptions.commands) {
|
||||||
|
userOptions.commands = {
|
||||||
|
zoom: [],
|
||||||
|
crop: [],
|
||||||
|
stretch: [],
|
||||||
|
pan: [],
|
||||||
|
internal: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
userOptions.commands.zoom = [{
|
||||||
|
action: 'change-zoom',
|
||||||
|
label: 'Zoom +5%',
|
||||||
|
arguments: {
|
||||||
|
zoom: 0.05
|
||||||
|
},
|
||||||
|
shortcut: {
|
||||||
|
key: 'z',
|
||||||
|
code: 'KeyY',
|
||||||
|
ctrlKey: false,
|
||||||
|
metaKey: false,
|
||||||
|
altKey: false,
|
||||||
|
shiftKey: false,
|
||||||
|
onKeyUp: true,
|
||||||
|
onKeyDown: false,
|
||||||
|
},
|
||||||
|
internalOnly: true,
|
||||||
|
actionId: 'change-zoom-10in'
|
||||||
|
}, {
|
||||||
|
action: 'change-zoom',
|
||||||
|
label: 'Zoom -5%',
|
||||||
|
arguments: {
|
||||||
|
zoom: -0.05
|
||||||
|
},
|
||||||
|
shortcut: {
|
||||||
|
key: 'u',
|
||||||
|
code: 'KeyU',
|
||||||
|
ctrlKey: false,
|
||||||
|
metaKey: false,
|
||||||
|
altKey: false,
|
||||||
|
shiftKey: false,
|
||||||
|
onKeyUp: true,
|
||||||
|
onKeyDown: false,
|
||||||
|
},
|
||||||
|
internalOnly: true,
|
||||||
|
actionId: 'change-zoom-10out'
|
||||||
|
}, {
|
||||||
|
action: 'set-zoom',
|
||||||
|
label: 'Reset zoom',
|
||||||
|
arguments: {
|
||||||
|
zoom: 1,
|
||||||
|
},
|
||||||
|
internalOnly: true,
|
||||||
|
actionId: 'set-zoom-reset'
|
||||||
|
}];
|
||||||
|
|
||||||
|
delete (userOptions as any).actions;
|
||||||
|
|
||||||
|
userOptions.dev = {
|
||||||
|
loadFromSnapshot: false
|
||||||
|
};
|
||||||
|
userOptions.ui.dev = {
|
||||||
|
aardDebugOverlay: {
|
||||||
|
showOnStartup: false,
|
||||||
|
showDetectionDetails: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const newZoomActions = [{
|
||||||
|
action: 'set-zoom',
|
||||||
|
label: 'Reset zoom',
|
||||||
|
shortcut: {
|
||||||
|
key: 'r',
|
||||||
|
code: 'KeyR',
|
||||||
|
ctrlKey: false,
|
||||||
|
metaKey: false,
|
||||||
|
altKey: false,
|
||||||
|
shiftKey: true,
|
||||||
|
onKeyUp: true,
|
||||||
|
onKeyDown: false,
|
||||||
|
},
|
||||||
|
arguments: {
|
||||||
|
zoom: 1
|
||||||
|
},
|
||||||
|
internalOnly: true,
|
||||||
|
actionId: 'set-zoom-reset'
|
||||||
|
}, {
|
||||||
|
action: 'set-ar-zoom',
|
||||||
|
label: 'Automatic',
|
||||||
|
comment: 'Automatically detect aspect ratio and zoom accordingly',
|
||||||
|
arguments: {
|
||||||
|
type: AspectRatioType.Automatic
|
||||||
|
},
|
||||||
|
shortcut: {
|
||||||
|
key: 'a',
|
||||||
|
code: 'KeyA',
|
||||||
|
ctrlKey: false,
|
||||||
|
metaKey: false,
|
||||||
|
altKey: false,
|
||||||
|
shiftKey: true,
|
||||||
|
onKeyUp: true,
|
||||||
|
onKeyDown: false,
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
action: 'set-ar-zoom',
|
||||||
|
label: 'Cycle',
|
||||||
|
comment: 'Cycle through zoom options',
|
||||||
|
arguments: {
|
||||||
|
type: AspectRatioType.Cycle
|
||||||
|
},
|
||||||
|
shortcut: {
|
||||||
|
key: 'c',
|
||||||
|
code: 'KeyC',
|
||||||
|
ctrlKey: false,
|
||||||
|
metaKey: false,
|
||||||
|
altKey: false,
|
||||||
|
shiftKey: true,
|
||||||
|
onKeyUp: true,
|
||||||
|
onKeyDown: false,
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
action: 'set-ar-zoom',
|
||||||
|
label: '21:9',
|
||||||
|
comment: 'Zoom for 21:9 aspect ratio (1:2.39)',
|
||||||
|
arguments: {
|
||||||
|
type: AspectRatioType.Fixed,
|
||||||
|
ratio: 2.39
|
||||||
|
},
|
||||||
|
shortcut: {
|
||||||
|
key: 'd',
|
||||||
|
code: 'KeyD',
|
||||||
|
ctrlKey: false,
|
||||||
|
metaKey: false,
|
||||||
|
altKey: false,
|
||||||
|
shiftKey: true,
|
||||||
|
onKeyUp: false,
|
||||||
|
onKeyDown: true,
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
action: 'set-ar-zoom',
|
||||||
|
label: '18:9',
|
||||||
|
comment: 'Zoom for 18:9 aspect ratio (1:2)',
|
||||||
|
arguments: {
|
||||||
|
type: AspectRatioType.Fixed,
|
||||||
|
ratio: 1.78
|
||||||
|
},
|
||||||
|
shortcut: {
|
||||||
|
key: 's',
|
||||||
|
code: 'KeyS',
|
||||||
|
ctrlKey: false,
|
||||||
|
metaKey: false,
|
||||||
|
altKey: false,
|
||||||
|
shiftKey: true,
|
||||||
|
onKeyUp: false,
|
||||||
|
onKeyDown: true,
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
action: 'set-ar-zoom',
|
||||||
|
label: '32:9',
|
||||||
|
comment: 'Zoom for 32:9 aspect ratio',
|
||||||
|
arguments: {
|
||||||
|
type: AspectRatioType.Fixed,
|
||||||
|
ratio: 3.56
|
||||||
|
},
|
||||||
|
}];
|
||||||
|
|
||||||
|
const compareShortcuts = (a: any, b: any) => {
|
||||||
|
if (!a || !b) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.key === b.key && b.code === b.code && a.ctrlKey == b.ctrlKey && a.shiftKey == b.shiftKey && a.metaKey == a.metaKey && a.altKey == b.altKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasConflict = (shortcut: any) => {
|
||||||
|
for (const ct in userOptions.commands) {
|
||||||
|
for (const command of userOptions.commands[ct]) {
|
||||||
|
if (compareShortcuts(shortcut, command.shortcut)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const zoomAction of newZoomActions) {
|
||||||
|
if (
|
||||||
|
!userOptions.commands.zoom.find(
|
||||||
|
x => x.action === zoomAction.action
|
||||||
|
&& x.arguments?.type === zoomAction.arguments?.type
|
||||||
|
&& x.arguments?.ratio === zoomAction.arguments?.ratio
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
userOptions.commands.zoom.push({
|
||||||
|
...zoomAction,
|
||||||
|
shortcut: hasConflict(zoomAction.shortcut) ? undefined : zoomAction.shortcut
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
forVersion: '6.3.92',
|
||||||
|
updateFn: (userOptions: any) => {
|
||||||
|
// applyToEmbeddedContent is now an enum, and also no longer optional
|
||||||
|
for (const site in userOptions.sites) {
|
||||||
|
if (userOptions.sites[site].applyToEmbeddedContent === undefined) {
|
||||||
|
userOptions.sites[site].applyToEmbeddedContent = EmbeddedContentSettingsOverridePolicy.UseAsDefault;
|
||||||
|
} else {
|
||||||
|
userOptions.sites[site].applyToEmbeddedContent = userOptions.sites[site].applyToEmbeddedContent ? EmbeddedContentSettingsOverridePolicy.Always : EmbeddedContentSettingsOverridePolicy.Never;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
forVersion: '6.3.93',
|
||||||
|
updateFn: (userOptions: SettingsInterface, defaultOptions: SettingsInterface) => {
|
||||||
|
(userOptions as any).arDetect.polling = defaultOptions.aard.polling;
|
||||||
|
(userOptions as any).arDetect.subtitles = defaultOptions.aard.subtitles;
|
||||||
|
(userOptions as any).arDetect.autoDisable = defaultOptions.aard.autoDisable;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
forVersion: '6.3.98',
|
||||||
|
updateFn: (userOptions: SettingsInterface, defaultOptions: SettingsInterface) => {
|
||||||
|
(userOptions as any).arDetect.letterboxOrientationScan = defaultOptions.aard.letterboxOrientationScan;
|
||||||
|
(userOptions as any).arDetect.edgeDetection = defaultOptions.aard.edgeDetection;
|
||||||
|
(userOptions as any).arDetect.subtitles = defaultOptions.aard.subtitles;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
forVersion: '6.3.98',
|
||||||
|
updateFn: (userOptions: SettingsInterface, defaultOptions: SettingsInterface) => {
|
||||||
|
userOptions.aard = defaultOptions.aard;
|
||||||
|
userOptions.aardLegacy = defaultOptions.aardLegacy;
|
||||||
|
delete (userOptions as any).arDetect;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
forVersion: '6.3.994',
|
||||||
|
updateFn: (userOptions: SettingsInterface, defaultOptions: SettingsInterface, logger?) => {
|
||||||
|
const convertLegacyExtensionMode = (option: {normal: LegacyExtensionMode, theater: LegacyExtensionMode, fullscreen: LegacyExtensionMode}) => {
|
||||||
|
if (typeof option === 'number') {
|
||||||
|
logger.warn('updateFn', 'This option is not an object, which suggests it has already been converted. Skipping conversion. Raw value of option:', option);
|
||||||
|
return option;
|
||||||
|
}
|
||||||
|
logger.log(
|
||||||
|
'updateFn',
|
||||||
|
`\nconverting option ——— normal: ${LegacyExtensionMode[option.normal]} theater: ${LegacyExtensionMode[option.normal]} fs: ${LegacyExtensionMode[option.fullscreen]}`, '\nraw obj:', option
|
||||||
|
);
|
||||||
|
if (option.normal === LegacyExtensionMode.Enabled) {
|
||||||
|
return ExtensionMode.All;
|
||||||
|
}
|
||||||
|
if (option.theater === LegacyExtensionMode.Enabled) {
|
||||||
|
return ExtensionMode.Theater;
|
||||||
|
}
|
||||||
|
if (option.fullscreen === LegacyExtensionMode.Enabled) {
|
||||||
|
return ExtensionMode.FullScreen;
|
||||||
|
}
|
||||||
|
if (option.fullscreen === LegacyExtensionMode.Disabled) {
|
||||||
|
return ExtensionMode.Disabled;
|
||||||
|
}
|
||||||
|
return ExtensionMode.Default;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const key in userOptions.sites) {
|
||||||
|
logger.log('updateFn', '\n\n ... migrating default enable-state for site', key);
|
||||||
|
userOptions.sites[key].enable = convertLegacyExtensionMode(userOptions.sites[key].enable as any);
|
||||||
|
userOptions.sites[key].enableAard = convertLegacyExtensionMode(userOptions.sites[key].enableAard as any);
|
||||||
|
|
||||||
|
if (key === '@global') {
|
||||||
|
userOptions.sites['@global'].enableKeyboard = userOptions.kbm.enabled && userOptions.kbm.keyboardEnabled ? InputHandlingMode.Enabled : InputHandlingMode.Disabled;
|
||||||
|
} else {
|
||||||
|
userOptions.sites[key].enableKeyboard = InputHandlingMode.Default;
|
||||||
|
}
|
||||||
|
|
||||||
|
userOptions.sites[key].enableUI = convertLegacyExtensionMode(
|
||||||
|
userOptions.sites[key].enableUI ?? (key === '@global' ? ExtensionMode.FullScreen : ExtensionMode.Default) as any
|
||||||
|
);
|
||||||
|
|
||||||
|
logger.log('updateFn', 'migrated site', key, userOptions.sites[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
forVersion: '6.3.995',
|
||||||
|
updateFn: (userOptions: SettingsInterface, defaultOptions: SettingsInterface, logger?) => {
|
||||||
|
if (!userOptions.ui) {
|
||||||
|
userOptions.ui = defaultOptions.ui
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
forVersion: '6.3.996',
|
||||||
|
updateFn: (userOptions: SettingsInterface, defaultOptions: SettingsInterface, logger?) => {
|
||||||
|
for (const site in userOptions.sites) {
|
||||||
|
const siteData = userOptions.sites[site];
|
||||||
|
logger.log('updateFn', 'migrating settings for', site, ' — persistCSA?', siteData.persistCSA, 'typeof persistCSA?', typeof siteData.persistCSA, 'does domconfig exist?', siteData.DOMConfig);
|
||||||
|
|
||||||
|
if (typeof siteData.persistCSA !== 'number') {
|
||||||
|
userOptions.sites[site].persistCSA = CropModePersistence.Default;
|
||||||
|
} else {
|
||||||
|
userOptions.sites[site].persistCSA = userOptions.sites[site].persistCSA ?? CropModePersistence.Default;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userOptions.sites[site].type as any === 'user-added' || userOptions.sites[site].type === 'user-defined') {
|
||||||
|
userOptions.sites[site].type = SiteSupportLevel.UserDefined;
|
||||||
|
}
|
||||||
|
if (userOptions.sites[site].type as any === 'no-support') {
|
||||||
|
userOptions.sites[site].type = SiteSupportLevel.Unknown;
|
||||||
|
}
|
||||||
|
if (userOptions.sites[site].defaultType as any === 'user-added' || userOptions.sites[site].defaultType === 'user-defined') {
|
||||||
|
userOptions.sites[site].type = SiteSupportLevel.UserDefined;
|
||||||
|
}
|
||||||
|
if (userOptions.sites[site].defaultType as any === 'no-support') {
|
||||||
|
userOptions.sites[site].defaultType = SiteSupportLevel.Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (siteData.activeDOMConfig?.startsWith('community') || siteData.activeDOMConfig === 'official' || siteData.activeDOMConfig === 'empty' || siteData.activeDOMConfig === 'auto') {
|
||||||
|
siteData.activeDOMConfig = `@${siteData.activeDOMConfig}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const domConf in siteData.DOMConfig) {
|
||||||
|
logger.log('updateFn', "Updating domconf", domConf);
|
||||||
|
const oldConf = userOptions.sites[site].DOMConfig[domConf] as any;
|
||||||
|
logger.log('updateFn', "——— oldConf:", oldConf);
|
||||||
|
|
||||||
|
const newConf: any = {
|
||||||
|
type: oldConf.type ?? userOptions.sites[site].type,
|
||||||
|
elements: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (newConf.type === 'user-added' || newConf.type === 'user-defined') {
|
||||||
|
newConf.type = SiteSupportLevel.UserDefined;
|
||||||
|
}
|
||||||
|
if (newConf.type === 'no-support') {
|
||||||
|
newConf.type = SiteSupportLevel.Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldConf.elements?.player) {
|
||||||
|
newConf.elements['player'] = {
|
||||||
|
detectionMode: oldConf?.elements?.player?.manual ? (
|
||||||
|
oldConf?.elements?.player?.querySelectors.trim() ? PlayerDetectionMode.QuerySelectors : PlayerDetectionMode.AncestorIndex
|
||||||
|
) : PlayerDetectionMode.Auto,
|
||||||
|
allowAutoFallback: true,
|
||||||
|
ancestorIndex: oldConf?.elements?.player?.parentIndex,
|
||||||
|
querySelectors: oldConf?.elements?.player?.querySelectors,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newConf.elements['player'] = {
|
||||||
|
detectionMode: PlayerDetectionMode.Auto,
|
||||||
|
allowAutoFallback: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (oldConf.customCss) {
|
||||||
|
newConf.customCss = oldConf.customCss;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldConf.elements?.video) {
|
||||||
|
newConf.elements['video'] = {
|
||||||
|
type: oldConf.type ?? siteData.type,
|
||||||
|
elements: {
|
||||||
|
video: {
|
||||||
|
playerDetectionMode: oldConf?.elements?.video?.manual ? PlayerDetectionMode.QuerySelectors : PlayerDetectionMode.Auto,
|
||||||
|
allowAutoFallback: true,
|
||||||
|
querySelectors: oldConf?.elements?.video?.querySelectors,
|
||||||
|
customCSS: oldConf?.elements?.video?.customCss,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// migrate names — official and community options get @ at the start
|
||||||
|
let domConfName = domConf;
|
||||||
|
if (domConfName.startsWith('community') || domConfName === 'official' || domConfName === 'empty' || domConfName === 'auto') {
|
||||||
|
domConfName = `@${domConfName}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (domConfName !== domConf) {
|
||||||
|
logger.warn(`updateFn`, '\n\nnaming for default domConf has changed. Old:', domConf, 'new:', domConfName);
|
||||||
|
delete userOptions.sites[site].DOMConfig[domConf];
|
||||||
|
}
|
||||||
|
|
||||||
|
userOptions.sites[site].DOMConfig[domConfName] = newConf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set new defaults for global and empty:
|
||||||
|
userOptions.sites['@global'].DOMConfig = {
|
||||||
|
'@auto': {
|
||||||
|
type: SiteSupportLevel.Unknown,
|
||||||
|
elements: {
|
||||||
|
player: {
|
||||||
|
detectionMode: PlayerDetectionMode.Auto,
|
||||||
|
allowAutoFallback: true,
|
||||||
|
},
|
||||||
|
video: {
|
||||||
|
detectionMode: PlayerDetectionMode.Auto,
|
||||||
|
allowAutoFallback: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
userOptions.sites['@global'].activeDOMConfig = '@auto';
|
||||||
|
userOptions.sites['@empty'].DOMConfig = {
|
||||||
|
'@empty': {
|
||||||
|
type: SiteSupportLevel.UserDefined,
|
||||||
|
elements: {
|
||||||
|
player: {
|
||||||
|
detectionMode: PlayerDetectionMode.Auto,
|
||||||
|
allowAutoFallback: true,
|
||||||
|
},
|
||||||
|
video: {
|
||||||
|
detectionMode: PlayerDetectionMode.Auto,
|
||||||
|
allowAutoFallback: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
userOptions.sites['@empty'].activeDOMConfig = '@empty';
|
||||||
|
|
||||||
|
logger.log('updateFn', 'Migration complete. New site settings:', userOptions.sites);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
forVersion: '6.3.997',
|
||||||
|
updateFn: (userOptions: SettingsInterface, defaultOptions: SettingsInterface, logger?) => {
|
||||||
|
|
||||||
|
// default zoom key combinations that involved the 'shift' key should have
|
||||||
|
// shortcut.key in uppercase, but they didn't.
|
||||||
|
for (const command of userOptions.commands?.zoom) {
|
||||||
|
if (command.shortcut) {
|
||||||
|
if (command.shortcut.shiftKey) {
|
||||||
|
if (command.shortcut.key.toUpperCase() === command.shortcut.code.charAt(3)) {
|
||||||
|
command.shortcut.key = command.shortcut.key.toUpperCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
forVersion: '6.3.998',
|
||||||
|
updateFn: (userOptions: SettingsInterface, defaultOptions: SettingsInterface, logger?) => {
|
||||||
|
if (!userOptions.sites["www.amazon.com"]) {
|
||||||
|
userOptions.sites["www.amazon.com"] = _cp(defaultOptions.sites["www.amazon.com"] );
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userOptions.commands?.zoom) {
|
||||||
|
const firstFixed = userOptions.commands?.zoom.findIndex(x => x.arguments.type === AspectRatioType.Fixed);
|
||||||
|
|
||||||
|
if (firstFixed === -1) {
|
||||||
|
userOptions.commands.zoom.push({
|
||||||
|
action: 'set-ar-zoom',
|
||||||
|
label: 'Cover',
|
||||||
|
comment: 'Covers the entire screen, cropping as much as needed',
|
||||||
|
arguments: {
|
||||||
|
type: AspectRatioType.Cover
|
||||||
|
},
|
||||||
|
shortcut: {
|
||||||
|
key: 'w',
|
||||||
|
code: 'KeyW',
|
||||||
|
ctrlKey: false,
|
||||||
|
metaKey: false,
|
||||||
|
altKey: false,
|
||||||
|
shiftKey: true,
|
||||||
|
onKeyUp: true,
|
||||||
|
onKeyDown: false,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
userOptions.commands.zoom.splice(firstFixed, 0, {
|
||||||
|
action: 'set-ar-zoom',
|
||||||
|
label: 'Cover',
|
||||||
|
comment: 'Covers the entire screen, cropping as much as needed',
|
||||||
|
arguments: {
|
||||||
|
type: AspectRatioType.Cover
|
||||||
|
},
|
||||||
|
shortcut: {
|
||||||
|
key: 'w',
|
||||||
|
code: 'KeyW',
|
||||||
|
ctrlKey: false,
|
||||||
|
metaKey: false,
|
||||||
|
altKey: false,
|
||||||
|
shiftKey: true,
|
||||||
|
onKeyUp: true,
|
||||||
|
onKeyDown: false,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
export default ExtensionConfPatch;
|
||||||
File diff suppressed because it is too large
Load Diff
1101
src/ext/conf/ExtensionConf.ts
Normal file
1101
src/ext/conf/ExtensionConf.ts
Normal file
File diff suppressed because it is too large
Load Diff
5
src/ext/enum/run-level.enum.ts
Normal file
5
src/ext/enum/run-level.enum.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export enum RunLevel {
|
||||||
|
Off = 0,
|
||||||
|
UIOnly = 1,
|
||||||
|
CustomCSSActive = 2
|
||||||
|
}
|
||||||
@ -1,274 +0,0 @@
|
|||||||
import Debug from '../conf/Debug';
|
|
||||||
import PlayerData from './video-data/PlayerData';
|
|
||||||
import ExtensionMode from '../../common/enums/extension-mode.enum';
|
|
||||||
|
|
||||||
class ActionHandler {
|
|
||||||
|
|
||||||
constructor(pageInfo) {
|
|
||||||
this.logger = pageInfo.logger;
|
|
||||||
this.pageInfo = pageInfo;
|
|
||||||
this.settings = pageInfo.settings;
|
|
||||||
|
|
||||||
this.inputs = ['input', 'select', 'button', 'textarea'];
|
|
||||||
this.keyboardLocalDisabled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
this.logger.log('info', 'debug', "[ActionHandler::init] starting init");
|
|
||||||
|
|
||||||
this.keyUpActions = [];
|
|
||||||
this.keyDownActions = [];
|
|
||||||
this.mouseMoveActions = [];
|
|
||||||
this.mouseScrollUpActions = [];
|
|
||||||
this.mouseScrollDownActions = [];
|
|
||||||
this.mouseEnterActions = [];
|
|
||||||
this.mouseLeaveActions = [];
|
|
||||||
|
|
||||||
var ths = this;
|
|
||||||
|
|
||||||
var actions;
|
|
||||||
try {
|
|
||||||
if (this.settings.active.sites[window.location.host].actions) {
|
|
||||||
actions = this.settings.active.sites[window.location.host].actions;
|
|
||||||
} else {
|
|
||||||
actions = this.settings.active.actions;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
actions = this.settings.active.actions;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
for (var action of actions) {
|
|
||||||
if (!action.scopes) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (var scope in action.scopes) {
|
|
||||||
if (! action.scopes[scope].shortcut) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var shortcut = action.scopes[scope].shortcut[0];
|
|
||||||
if (shortcut.onKeyDown) {
|
|
||||||
this.keyDownActions.push({
|
|
||||||
shortcut: shortcut,
|
|
||||||
cmd: action.cmd,
|
|
||||||
scope: scope,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (shortcut.onKeyUp) {
|
|
||||||
this.keyUpActions.push({
|
|
||||||
shortcut: shortcut,
|
|
||||||
cmd: action.cmd,
|
|
||||||
scope: scope,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (shortcut.onScrollUp) {
|
|
||||||
this.mouseScrollUpActions.push({
|
|
||||||
shortcut: shortcut,
|
|
||||||
cmd: action.cmd,
|
|
||||||
scope: scope,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (shortcut.onScrollDown) {
|
|
||||||
this.mouseScrollDownActions.push({
|
|
||||||
shortcut: shortcut,
|
|
||||||
cmd: action.cmd,
|
|
||||||
scope: scope,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (shortcut.onMouseEnter) {
|
|
||||||
this.mouseEnterActions.push({
|
|
||||||
shortcut: shortcut,
|
|
||||||
cmd: action.cmd,
|
|
||||||
scope: scope,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (shortcut.onMouseLeave) {
|
|
||||||
this.mouseLeaveActions.push({
|
|
||||||
shortcut: shortcut,
|
|
||||||
cmd: action.cmd,
|
|
||||||
scope: scope,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (shortcut.onMouseMove) {
|
|
||||||
this.mouseMoveActions.push({
|
|
||||||
shortcut: shortcut,
|
|
||||||
cmd: action.cmd,
|
|
||||||
scope: scope,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener('keydown', (event) => ths.handleKeydown(event) );
|
|
||||||
document.addEventListener('keyup', (event) => ths.handleKeyup(event) );
|
|
||||||
|
|
||||||
this.pageInfo.setActionHandler(this);
|
|
||||||
|
|
||||||
this.logger.log('info', 'debug', "[ActionHandler::init] initialization complete");
|
|
||||||
}
|
|
||||||
|
|
||||||
registerHandleMouse(videoData) {
|
|
||||||
this.logger.log('info', ['actionHandler', 'mousemove'], "[ActionHandler::registerHandleMouse] registering handle mouse for videodata:", videoData)
|
|
||||||
|
|
||||||
var ths = this;
|
|
||||||
if (videoData.player && videoData.player.element) {
|
|
||||||
videoData.player.element.addEventListener('mousemove', (event) => ths.handleMouseMove(event, videoData));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unregisterHandleMouse(videoData) {
|
|
||||||
var ths = this;
|
|
||||||
if (videoData.player && videoData.player.element) {
|
|
||||||
videoData.player.element.removeEventListener('mousemove', (event) => ths.handleMouseMove(event, videoData));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setKeyboardLocal(state) {
|
|
||||||
if (state === ExtensionMode.Enabled) {
|
|
||||||
this.keyboardLocalDisabled = false;
|
|
||||||
} else if (state === ExtensionMode.Disabled) {
|
|
||||||
this.keyboardLocalDisabled = true;
|
|
||||||
}
|
|
||||||
// don't do shit on invalid value of state
|
|
||||||
}
|
|
||||||
|
|
||||||
preventAction() {
|
|
||||||
var activeElement = document.activeElement;
|
|
||||||
|
|
||||||
if(this.logger.canLog('keyboard')) {
|
|
||||||
this.logger.pause(); // temp disable to avoid recursing;
|
|
||||||
|
|
||||||
this.logger.log('info', 'keyboard', "[ActionHandler::preventAction] Testing whether we're in a textbox or something. Detailed rundown of conditions:\n" +
|
|
||||||
"is full screen? (yes->allow):", PlayerData.isFullScreen(),
|
|
||||||
"\nis tag one of defined inputs? (yes->prevent):", this.inputs.indexOf(activeElement.tagName.toLocaleLowerCase()) !== -1,
|
|
||||||
"\nis role = textbox? (yes -> prevent):", activeElement.getAttribute("role") === "textbox",
|
|
||||||
"\nis type === 'text'? (yes -> prevent):", activeElement.getAttribute("type") === "text",
|
|
||||||
"\nis keyboard local disabled? (yes -> prevent):", this.keyboardLocalDisabled,
|
|
||||||
"\nis keyboard enabled in settings? (no -> prevent)", this.settings.keyboardShortcutsEnabled(window.location.hostname),
|
|
||||||
"\nwill the action be prevented? (yes -> prevent)", this.preventAction(),
|
|
||||||
"\n-----------------{ extra debug info }-------------------",
|
|
||||||
"\ntag name? (lowercase):", activeElement.tagName, activeElement.tagName.toLocaleLowerCase(),
|
|
||||||
"\nrole:", activeElement.getAttribute('role'),
|
|
||||||
"\ntype:", activeElement.getAttribute('type'),
|
|
||||||
"insta-fail inputs:", this.inputs
|
|
||||||
);
|
|
||||||
|
|
||||||
this.logger.resume(); // undisable
|
|
||||||
}
|
|
||||||
|
|
||||||
// lately youtube has allowed you to read and write comments while watching video in
|
|
||||||
// fullscreen mode. We can no longer do this.
|
|
||||||
// if (PlayerData.isFullScreen()) {
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (this.keyboardLocalDisabled) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!this.settings.keyboardShortcutsEnabled(window.location.hostname)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (this.inputs.indexOf(activeElement.tagName.toLocaleLowerCase()) !== -1) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (activeElement.getAttribute("role") === "textbox") {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (activeElement.getAttribute("type") === "text") {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
isActionMatch(shortcut, event) {
|
|
||||||
return shortcut.key === event.key &&
|
|
||||||
shortcut.ctrlKey === event.ctrlKey &&
|
|
||||||
shortcut.metaKey === event.metaKey &&
|
|
||||||
shortcut.altKey === event.altKey &&
|
|
||||||
shortcut.shiftKey === event.shiftKey
|
|
||||||
}
|
|
||||||
|
|
||||||
execAction(actions, event, videoData) {
|
|
||||||
this.logger.log('info', 'keyboard', "%c[ActionHandler::execAction] Trying to find and execute action for event. Actions/event: ", "color: #ff0", actions, event);
|
|
||||||
|
|
||||||
for (var action of actions) {
|
|
||||||
if (this.isActionMatch(action.shortcut, event)) {
|
|
||||||
this.logger.log('info', 'keyboard', "%c[ActionHandler::execAction] found an action associated with keypress/event: ", "color: #ff0", action);
|
|
||||||
|
|
||||||
for (var cmd of action.cmd) {
|
|
||||||
if (action.scope === 'page') {
|
|
||||||
if (cmd.action === "set-ar") {
|
|
||||||
this.pageInfo.setAr({type: cmd.arg, ratio: cmd.customArg});
|
|
||||||
} else if (cmd.action === "change-zoom") {
|
|
||||||
this.pageInfo.zoomStep(cmd.arg);
|
|
||||||
} else if (cmd.action === "set-zoom") {
|
|
||||||
this.pageInfo.setZoom(cmd.arg);
|
|
||||||
} else if (cmd.action === "set-stretch") {
|
|
||||||
this.pageInfo.setStretchMode(cmd.arg);
|
|
||||||
} else if (cmd.action === "toggle-pan") {
|
|
||||||
this.pageInfo.setPanMode(cmd.arg)
|
|
||||||
} else if (cmd.action === "pan") {
|
|
||||||
if (videoData) {
|
|
||||||
videoData.panHandler(event, true);
|
|
||||||
}
|
|
||||||
} else if (cmd.action === 'set-keyboard') {
|
|
||||||
this.setKeyboardLocal(cmd.arg);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let site = action.scope === 'site' ? window.location.host : '@global';
|
|
||||||
|
|
||||||
if (cmd.action === "set-stretch") {
|
|
||||||
this.settings.active.sites[site].stretch = cmd.arg;
|
|
||||||
} else if (cmd.action === "set-alignment") {
|
|
||||||
this.settings.active.sites[site].videoAlignment = cmd.arg;
|
|
||||||
} else if (cmd.action === "set-extension-mode") {
|
|
||||||
this.settings.active.sites[site].status = cmd.arg;
|
|
||||||
} else if (cmd.action === "set-autoar-mode") {
|
|
||||||
this.settings.active.sites[site].arStatus = cmd.arg;
|
|
||||||
} else if (cmd.action === 'set-keyboard') {
|
|
||||||
this.settings.active.sites[site].keyboardShortcutsEnabled = cmd.arg;
|
|
||||||
}
|
|
||||||
this.settings.save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// če smo našli dejanje za to tipko, potem ne preiskujemo naprej
|
|
||||||
// if we found an action for this key, we stop searching for a match
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
handleKeyup(event) {
|
|
||||||
this.logger.log('info', 'keyboard', "%c[ActionHandler::handleKeyup] we pressed a key: ", "color: #ff0", event.key , " | keyup: ", event.keyup, "event:", event);
|
|
||||||
|
|
||||||
if (this.preventAction()) {
|
|
||||||
this.logger.log('info', 'keyboard', "[ActionHandler::handleKeyup] we are in a text box or something. Doing nothing.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.execAction(this.keyUpActions, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleKeydown(event) {
|
|
||||||
this.logger.log('info', 'keyboard', "%c[ActionHandler::handleKeydown] we pressed a key: ", "color: #ff0", event.key , " | keydown: ", event.keydown, "event:", event)
|
|
||||||
|
|
||||||
if (this.preventAction()) {
|
|
||||||
this.logger.log('info', 'keyboard', "[ActionHandler::handleKeydown] we are in a text box or something. Doing nothing.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.execAction(this.keyDownActions, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMouseMove(event, videoData) {
|
|
||||||
this.logger.log('info', 'keyboard', "[ActionHandler::handleMouseMove] mouse move is being handled.\nevent:", event, "\nvideo data:", videoData);
|
|
||||||
videoData.panHandler(event);
|
|
||||||
this.execAction(this.mouseMoveActions, event, videoData)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ActionHandler;
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
class Interface {
|
|
||||||
constructor(videoData) {
|
|
||||||
this.conf = videoData;
|
|
||||||
this.player = videoData.player;
|
|
||||||
}
|
|
||||||
|
|
||||||
injectUi() {
|
|
||||||
this.detectorDiv = document.createElement('div');
|
|
||||||
this.uiRoot = document.createElement('div');
|
|
||||||
this.detectorDiv.appendChild(this.uiRoot);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,211 +0,0 @@
|
|||||||
import Debug from '../conf/Debug';
|
|
||||||
import currentBrowser from '../conf/BrowserDetect';
|
|
||||||
|
|
||||||
class Logger {
|
|
||||||
constructor(conf) {
|
|
||||||
this.initLogger();
|
|
||||||
if (conf) {
|
|
||||||
this.conf = conf;
|
|
||||||
}
|
|
||||||
this.history = [];
|
|
||||||
this.startTime = performance.now();
|
|
||||||
this.temp_disable = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
initLogger() {
|
|
||||||
const ths = this;
|
|
||||||
const br = currentBrowser.firefox ? browser : chrome;
|
|
||||||
br.storage.onChanged.addListener( (changes, area) => {
|
|
||||||
if (Debug.debug && Debug.debugStorage) {
|
|
||||||
console.log("[Logger::<storage/on change>] Settings have been changed outside of here. Updating active settings. Changes:", changes, "storage area:", area);
|
|
||||||
if (changes['uwLogger'] && changes['uwLogger'].newValue) {
|
|
||||||
console.log("[Logger::<storage/on change>] new settings object:", JSON.parse(changes.uwLogger.newValue));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(changes['uwLogger'] && changes['uwLogger'].newValue) {
|
|
||||||
ths.conf = JSON.parse(changes.uwLogger.newValue);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async init() {
|
|
||||||
if (!this.conf) {
|
|
||||||
this.conf = await this.getSaved();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
clear() {
|
|
||||||
this.log = [];
|
|
||||||
this.startTime = performance.now();
|
|
||||||
}
|
|
||||||
|
|
||||||
setConf(conf) {
|
|
||||||
this.conf = conf; // effective immediately
|
|
||||||
// also persist settings:
|
|
||||||
if (currentBrowser.firefox || currentBrowser.edge) {
|
|
||||||
return browser.storage.local.set( {'uwLogger': JSON.stringify(this.conf)});
|
|
||||||
} else if (currentBrowser.chrome) {
|
|
||||||
return chrome.storage.local.set( {'uwLogger': JSON.stringify(this.logger)});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getSaved() {
|
|
||||||
let ret;
|
|
||||||
|
|
||||||
if (currentBrowser.firefox) {
|
|
||||||
ret = await browser.storage.local.get('uwLogger');
|
|
||||||
} else if (currentBrowser.chrome) {
|
|
||||||
ret = await new Promise( (resolve, reject) => {
|
|
||||||
chrome.storage.local.get('uwLogger', (res) => resolve(res));
|
|
||||||
});
|
|
||||||
} else if (currentBrowser.edge) {
|
|
||||||
ret = await new Promise( (resolve, reject) => {
|
|
||||||
browser.storage.local.get('uwLogger', (res) => resolve(res));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Debug.debug && Debug.debugStorage) {
|
|
||||||
try {
|
|
||||||
console.log("[Logger::getSaved] Got settings:", JSON.parse(ret.uwLogger));
|
|
||||||
} catch (e) {
|
|
||||||
console.log("[Logger::getSaved] No settings.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return JSON.parse(ret.uwLogger);
|
|
||||||
} catch(e) {
|
|
||||||
return {logToFile: false, logToConsole: false, consoleOptions: {}, fileOptions: {}};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// allow syncing of start times between bg and page scripts.
|
|
||||||
// may result in negative times in the log file, but that doesn't
|
|
||||||
// really matter
|
|
||||||
getStartTime() {
|
|
||||||
return this.startTime;
|
|
||||||
}
|
|
||||||
setStartTime(startTime) {
|
|
||||||
if (startTime) {
|
|
||||||
this.startTime = startTime;
|
|
||||||
} else {
|
|
||||||
this.startTime = performance.now();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getLogFileString() {
|
|
||||||
let logfileStr = '';
|
|
||||||
let logTs = ''; // number of seconds since extension started on a given page¸
|
|
||||||
for (let i = 0; i < this.history.length; i++) {
|
|
||||||
logTs = ((this.history[i].ts - Math.floor(this.performance.now)) / 3).toFixed(3);
|
|
||||||
logfileStr = `${logfileStr}[@${logTs}] -- ${this.history[i].message}\n`
|
|
||||||
}
|
|
||||||
|
|
||||||
return logfileStr;
|
|
||||||
}
|
|
||||||
|
|
||||||
pause() {
|
|
||||||
this.temp_disable = true;
|
|
||||||
}
|
|
||||||
resume() {
|
|
||||||
this.temp_disable = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
canLog(component) {
|
|
||||||
return this.canLogFile(component) || this.canLogConsole(component);
|
|
||||||
}
|
|
||||||
|
|
||||||
canLogFile(component) {
|
|
||||||
if (!this.conf.fileOptions.enabled || this.temp_disable) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (component.length ) {
|
|
||||||
for (const c in component) {
|
|
||||||
if (this.conf.fileOptions[component]) {
|
|
||||||
return this.conf.fileOptions[component];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this.conf.fileOptions[component];
|
|
||||||
}
|
|
||||||
canLogConsole(component) {
|
|
||||||
if (!this.conf.consoleOptions.enabled || this.temp_disable) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (Array.isArray(component) && component.length) {
|
|
||||||
for (const c in component) {
|
|
||||||
if (this.conf.consoleOptions[component]) {
|
|
||||||
return this.conf.consoleOptions[component];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this.conf.consoleOptions[component];
|
|
||||||
}
|
|
||||||
|
|
||||||
// level is unused as of now, but this may change in the future
|
|
||||||
// levels: 'info', 'warn', 'error'
|
|
||||||
log(level, component, ...message) {
|
|
||||||
if (!this.conf) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.conf.logToFile) {
|
|
||||||
if (this.canLogFile(component)) {
|
|
||||||
let ts = performance.now();
|
|
||||||
if (ts <= this.history[this.history.length - 1]) {
|
|
||||||
ts = this.history[this.history.length - 1] + 0.00001;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.history.push({
|
|
||||||
ts: ts,
|
|
||||||
message: JSON.stringify(message),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.conf.logToConsole) {
|
|
||||||
if (this.canLogConsole(component)) {
|
|
||||||
console.log(...message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// leaves a noticeable mark in the file log at the time it got triggered, as well as
|
|
||||||
// at the intervals of 1s and .5s before the trigger moment
|
|
||||||
cahen() {
|
|
||||||
if (this.conf.logToFile) {
|
|
||||||
let ts = performance.now();
|
|
||||||
let secondMark = ts - 1000;
|
|
||||||
let halfSecondMark = ts - 500;
|
|
||||||
let i = this.history.length();
|
|
||||||
|
|
||||||
// correct ts _after_ secondMark and halfSecondMark were determined
|
|
||||||
if (ts <= this.history[this.history.length - 1]) {
|
|
||||||
ts = this.history[this.history.length - 1] + 0.00001;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.history.push({
|
|
||||||
ts: ts,
|
|
||||||
message: "-------------------------------------- CAHEN --------------------------------------"
|
|
||||||
});
|
|
||||||
|
|
||||||
// find the spot for the half second mark. In this case, we don't really particularly care whether timestamps
|
|
||||||
// are duped due to cahen warnings
|
|
||||||
while (this.history[i--].ts > halfSecondMark) {}
|
|
||||||
this.history.push({
|
|
||||||
ts: halfSecondMark,
|
|
||||||
message: "-------------------------------------- 0.5s to CAHEN --------------------------------------"
|
|
||||||
});
|
|
||||||
|
|
||||||
// repeat for one second warning
|
|
||||||
while (this.history[i--].ts > secondMark) {}
|
|
||||||
this.history.push({
|
|
||||||
ts: secondMark,
|
|
||||||
message: "-------------------------------------- 1s to CAHEN --------------------------------------"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Logger;
|
|
||||||
@ -1,189 +0,0 @@
|
|||||||
class PlayerPickerHelper {
|
|
||||||
constructor (settings, callbacks) {
|
|
||||||
this.settings = settings;
|
|
||||||
this.videos = document.selectElementsByTagName('video');
|
|
||||||
this.selectedParentIndex = this.findPlayerForVideos(settings, this.videos)[0];
|
|
||||||
this.savedCss = [];
|
|
||||||
this.markVideos();
|
|
||||||
this.markIndexForAll(index);
|
|
||||||
this.markInitialQuerySelectors();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
*
|
|
||||||
* Internal functions
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
saveBorder(element) {
|
|
||||||
if (this.savedCss.findIndex(x => x.element === element) !== -1) {
|
|
||||||
this.savedCss.push({element: element, border: element.style.border});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
restoreBorders() {
|
|
||||||
for (const e of this.savedCss) {
|
|
||||||
e.element.style.border = e.border;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
findPlayerForVideos(settings, videos) {
|
|
||||||
const playerIndexes = [];
|
|
||||||
for (const v of videos) {
|
|
||||||
playerIndexes.push(this.findPlayerForVideo(settings, v));
|
|
||||||
}
|
|
||||||
return playerIndexes;
|
|
||||||
}
|
|
||||||
|
|
||||||
findPlayerForVideo(settings, video) {
|
|
||||||
const host = window.location.host;
|
|
||||||
let element = video.parentNode;
|
|
||||||
|
|
||||||
if (this.settings.active.sites[host]
|
|
||||||
&& this.settings.active.sites[host].DOM
|
|
||||||
&& this.settings.active.sites[host].DOM.player
|
|
||||||
&& this.settings.active.sites[host].DOM.player.manual) {
|
|
||||||
if (this.settings.active.sites[host].DOM.player.useRelativeAncestor
|
|
||||||
&& this.settings.active.sites[host].DOM.player.videoAncestor) {
|
|
||||||
|
|
||||||
return this.settings.active.sites[host].DOM.player.videoAncestor;
|
|
||||||
} else if (this.settings.active.sites[host].DOM.player.querySelectors) {
|
|
||||||
const allSelectors = document.querySelectorAll(this.settings.active.sites[host].DOM.player.querySelectors);
|
|
||||||
let elementIndex = 1;
|
|
||||||
while (element && !this.collectionHas(allSelectors, element)) {
|
|
||||||
element = element.parentNode;
|
|
||||||
elementIndex++;
|
|
||||||
}
|
|
||||||
return elementIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let elementIndex = 0;
|
|
||||||
|
|
||||||
var trustCandidateAfterGrows = 2; // if candidate_width or candidate_height increases in either dimensions this many
|
|
||||||
// times, we say we found our player. (This number ignores weird elements)
|
|
||||||
// in case our <video> is bigger than player in one dimension but smaller in the other
|
|
||||||
// if site is coded properly, player can't be wider than that
|
|
||||||
var candidate_width = Math.max(element.offsetWidth, window.innerWidth);
|
|
||||||
var candidate_height = Math.max(element.offsetHeight, window.innerHeight);
|
|
||||||
var playerCandidateNode = element;
|
|
||||||
|
|
||||||
// if we haven't found element using fancy methods, we resort to the good old fashioned way
|
|
||||||
var grows = trustCandidateAfterGrows;
|
|
||||||
while(element != undefined){
|
|
||||||
// odstranimo čudne elemente, ti bi pokvarili zadeve
|
|
||||||
// remove weird elements, those would break our stuff
|
|
||||||
if ( element.offsetWidth == 0 || element.offsetHeight == 0){
|
|
||||||
element = element.parentNode;
|
|
||||||
elementIndex++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( element.offsetHeight <= candidate_height &&
|
|
||||||
element.offsetWidth <= candidate_width ){
|
|
||||||
|
|
||||||
// if we're in fullscreen, we only consider elements that are exactly as big as the monitor.
|
|
||||||
if( ! isFullScreen ||
|
|
||||||
(element.offsetWidth == window.innerWidth && element.offsetHeight == window.innerHeight) ){
|
|
||||||
|
|
||||||
playerCandidateNode = element;
|
|
||||||
candidate_width = element.offsetWidth;
|
|
||||||
candidate_height = element.offsetHeight;
|
|
||||||
|
|
||||||
grows = trustCandidateAfterGrows;
|
|
||||||
|
|
||||||
this.logger.log('info', 'debug', "Found new candidate for player. Dimensions: w:", candidate_width, "h:",candidate_height, "node:", playerCandidateNode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(grows --<= 0){
|
|
||||||
this.logger.log('info', 'playerDetect', "Current element grew in comparrison to the child. We probably found the player. breaking loop, returning current result");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
element = element.parentNode;
|
|
||||||
elementIndex++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return elementIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
markVideos() {
|
|
||||||
for (const v of this.videos) {
|
|
||||||
this.markVideo(v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
markVideo(video) {
|
|
||||||
this.saveBorder(video);
|
|
||||||
video.style.border = "1px solid #00f";
|
|
||||||
}
|
|
||||||
|
|
||||||
markIndexForAll(index){
|
|
||||||
for (const v of this.videos) {
|
|
||||||
this.markIndex(index, v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
markIndex(index, video) {
|
|
||||||
el = video.parentNode;
|
|
||||||
while (index --> 1) {
|
|
||||||
el = el.parentNode;
|
|
||||||
}
|
|
||||||
this.saveBorder(el);
|
|
||||||
el.style.border = "1px solid #88f";
|
|
||||||
}
|
|
||||||
markInitialQuerySelectors() {
|
|
||||||
try {
|
|
||||||
if (this.settings.active.sites[host].DOM.player.querySelectors.trim()) {
|
|
||||||
this.markQuerySelectorMatches(this.settings.active.sites[host].DOM.player.querySelectors);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// nothing to see here. something in that if spaghett is undefined, which causes
|
|
||||||
// everything to fail. Since this means we've got zero query string matches to mark,
|
|
||||||
// we just ignore the failure
|
|
||||||
}
|
|
||||||
}
|
|
||||||
markQuerySelectorMatches(qsString) {
|
|
||||||
const allSelectors = document.querySelectorAll(qsString);
|
|
||||||
for (e of allSelectors) {
|
|
||||||
this.saveBorder(e);
|
|
||||||
e.style.border = "1px dashed fd2";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
markQsPlayerDetection(qsString, index, video) {
|
|
||||||
let element = video.parentNode;
|
|
||||||
let elementIndex = 1;
|
|
||||||
|
|
||||||
const allSelectors = document.querySelectorAll(qsString);
|
|
||||||
while (element && !this.collectionHas(allSelectors, element)) {
|
|
||||||
element = element.parentNode;
|
|
||||||
elementIndex++;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.saveBorder(element)
|
|
||||||
if (elementIndex > index) {
|
|
||||||
element.style.border = "2px solid #f00"
|
|
||||||
} else if (elementIndex === index) {
|
|
||||||
element.style.border = "2px solid #027a5c"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* Function that actually interface with playerpicker and do stuff
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
setQuerySelectors(querySelectorString) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PlayerPickerHelper
|
|
||||||
@ -1,534 +0,0 @@
|
|||||||
import Debug from '../conf/Debug';
|
|
||||||
import currentBrowser from '../conf/BrowserDetect';
|
|
||||||
import ExtensionConf from '../conf/ExtensionConf';
|
|
||||||
import ExtensionMode from '../../common/enums/extension-mode.enum';
|
|
||||||
import ObjectCopy from '../lib/ObjectCopy';
|
|
||||||
import Stretch from '../../common/enums/stretch.enum';
|
|
||||||
import VideoAlignment from '../../common/enums/video-alignment.enum';
|
|
||||||
import ExtensionConfPatch from '../conf/ExtConfPatches';
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Settings {
|
|
||||||
|
|
||||||
constructor(options) {
|
|
||||||
// Options: activeSettings, updateCallback, logger
|
|
||||||
this.logger = options.logger;
|
|
||||||
const activeSettings = options.activeSettings;
|
|
||||||
const updateCallback = options.updateCallback;
|
|
||||||
|
|
||||||
this.active = activeSettings ? activeSettings : undefined;
|
|
||||||
this.default = ExtensionConf;
|
|
||||||
this.default['version'] = this.getExtensionVersion();
|
|
||||||
this.useSync = false;
|
|
||||||
this.version = undefined;
|
|
||||||
this.updateCallback = updateCallback;
|
|
||||||
|
|
||||||
const ths = this;
|
|
||||||
|
|
||||||
if(currentBrowser.firefox) {
|
|
||||||
browser.storage.onChanged.addListener( (changes, area) => {
|
|
||||||
this.logger.log('info', 'settings', "[Settings::<storage/on change>] Settings have been changed outside of here. Updating active settings. Changes:", changes, "storage area:", area);
|
|
||||||
if (changes['uwSettings'] && changes['uwSettings'].newValue) {
|
|
||||||
this.logger.log('info', 'settings',"[Settings::<storage/on change>] new settings object:", JSON.parse(changes.uwSettings.newValue));
|
|
||||||
}
|
|
||||||
if(changes['uwSettings'] && changes['uwSettings'].newValue) {
|
|
||||||
ths.setActive(JSON.parse(changes.uwSettings.newValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
if(this.updateCallback) {
|
|
||||||
try {
|
|
||||||
updateCallback(ths);
|
|
||||||
} catch (e) {
|
|
||||||
this.logger.log('error', 'settings', "[Settings] CALLING UPDATE CALLBACK FAILED.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (currentBrowser.chrome) {
|
|
||||||
chrome.storage.onChanged.addListener( (changes, area) => {
|
|
||||||
this.logger.log('info', 'settings', "[Settings::<storage/on change>] Settings have been changed outside of here. Updating active settings. Changes:", changes, "storage area:", area);
|
|
||||||
if (changes['uwSettings'] && changes['uwSettings'].newValue) {
|
|
||||||
this.logger.log('info', 'settings',"[Settings::<storage/on change>] new settings object:", JSON.parse(changes.uwSettings.newValue));
|
|
||||||
}
|
|
||||||
if(changes['uwSettings'] && changes['uwSettings'].newValue) {
|
|
||||||
ths.setActive(JSON.parse(changes.uwSettings.newValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
if(this.updateCallback) {
|
|
||||||
try {
|
|
||||||
updateCallback(ths);
|
|
||||||
} catch (e) {
|
|
||||||
this.logger.log('error', 'settings',"[Settings] CALLING UPDATE CALLBACK FAILED.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getExtensionVersion() {
|
|
||||||
if (currentBrowser.firefox) {
|
|
||||||
return browser.runtime.getManifest().version;
|
|
||||||
} else if (currentBrowser.chrome) {
|
|
||||||
return chrome.runtime.getManifest().version;
|
|
||||||
} else if (currentBrowser.edge) {
|
|
||||||
return browser.runtime.getManifest().version;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
compareExtensionVersions(a, b) {
|
|
||||||
let aa = a.split('.');
|
|
||||||
let bb = b.split('.');
|
|
||||||
|
|
||||||
if (+aa[0] !== +bb[0]) {
|
|
||||||
// difference on first digit
|
|
||||||
return +aa[0] - +bb[0];
|
|
||||||
} if (+aa[1] !== +bb[1]) {
|
|
||||||
// first digit same, difference on second digit
|
|
||||||
return +aa[1] - +bb[1];
|
|
||||||
} if (+aa[2] !== +bb[2]) {
|
|
||||||
return +aa[2] - +bb[2];
|
|
||||||
// first two digits the same, let's check the third digit
|
|
||||||
} else {
|
|
||||||
// fourth digit is optional. When not specified, 0 is implied
|
|
||||||
// btw, ++(aa[3] || 0) - ++(bb[3] || 0) doesn't work
|
|
||||||
|
|
||||||
// Since some things are easier if we actually have a value for
|
|
||||||
// the fourth digit, we turn a possible undefined into a zero
|
|
||||||
aa[3] = aa[3] === undefined ? 0 : aa[3];
|
|
||||||
bb[3] = bb[3] === undefined ? 0 : bb[3];
|
|
||||||
|
|
||||||
// also, the fourth digit can start with a letter.
|
|
||||||
// versions that start with a letter are ranked lower than
|
|
||||||
// versions x.x.x.0
|
|
||||||
if (isNaN(+aa[3]) ^ isNaN(+bb[3])) {
|
|
||||||
return isNaN(+aa[3]) ? -1 : 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// at this point, either both version numbers are a NaN or
|
|
||||||
// both versions are a number.
|
|
||||||
if (!isNaN(+aa[3])) {
|
|
||||||
return +aa[3] - +bb[3];
|
|
||||||
}
|
|
||||||
|
|
||||||
// letters have their own hierarchy:
|
|
||||||
// dev < a < b < rc
|
|
||||||
let av = this.getPrereleaseVersionHierarchy(aa[3]);
|
|
||||||
let bv = this.getPrereleaseVersionHierarchy(bb[3]);
|
|
||||||
|
|
||||||
if (av !== bv) {
|
|
||||||
return av - bv;
|
|
||||||
} else {
|
|
||||||
return +(aa[3].replace(/\D/g,'')) - +(bb[3].replace(/\D/g, ''));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getPrereleaseVersionHierarchy(version) {
|
|
||||||
if (version.startsWith('dev')) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if (version.startsWith('a')) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (version.startsWith('b')) {
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
return 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
sortConfPatches(patchesIn) {
|
|
||||||
return patchesIn.sort( (a, b) => this.compareExtensionVersions(a.forVersion, b.forVersion));
|
|
||||||
}
|
|
||||||
|
|
||||||
findFirstNecessaryPatch(version, extconfPatches) {
|
|
||||||
const sorted = this.sortConfPatches(extconfPatches);
|
|
||||||
return sorted.findIndex(x => this.compareExtensionVersions(x.forVersion, version) > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
applySettingsPatches(oldVersion, patches) {
|
|
||||||
let index = this.findFirstNecessaryPatch(oldVersion, patches);
|
|
||||||
if (index === -1) {
|
|
||||||
this.logger.log('info','settings','[Settings::applySettingsPatches] There are no pending conf patches.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// apply all remaining patches
|
|
||||||
this.logger.log('info', 'settings', `[Settings::applySettingsPatches] There are ${patches.length - index} settings patches to apply`);
|
|
||||||
while (index < patches.length) {
|
|
||||||
delete patches[index].forVersion;
|
|
||||||
ObjectCopy.overwrite(this.active, patches[index]);
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async init() {
|
|
||||||
const settings = await this.get();
|
|
||||||
this.version = this.getExtensionVersion();
|
|
||||||
|
|
||||||
// |—> on first setup, settings is undefined & settings.version is haram
|
|
||||||
// | since new installs ship with updates by default, no patching is
|
|
||||||
// | needed. In this case, we assume we're on the current version
|
|
||||||
const oldVersion = (settings && settings.version) || this.version;
|
|
||||||
|
|
||||||
if(Debug.debug) {
|
|
||||||
this.logger.log('info', 'settings', "[Settings::init] Configuration fetched from storage:", settings,
|
|
||||||
"\nlast saved with:", settings.version,
|
|
||||||
"\ncurrent version:", this.version
|
|
||||||
);
|
|
||||||
|
|
||||||
// if (Debug.flushStoredSettings) {
|
|
||||||
// this.logger.log('info', 'settings', "%c[Settings::init] Debug.flushStoredSettings is true. Using default settings", "background: #d00; color: #ffd");
|
|
||||||
// Debug.flushStoredSettings = false; // don't do it again this session
|
|
||||||
// this.active = this.getDefaultSettings();
|
|
||||||
// this.active.version = this.version;
|
|
||||||
// this.set(this.active);
|
|
||||||
// return this.active;
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
// if there's no settings saved, return default settings.
|
|
||||||
if(! settings || (Object.keys(settings).length === 0 && settings.constructor === Object)) {
|
|
||||||
this.logger.log(
|
|
||||||
'info',
|
|
||||||
'settings',
|
|
||||||
'[Settings::init] settings don\'t exist. Using defaults.\n#keys:',
|
|
||||||
settings ? Object.keys(settings).length : 0,
|
|
||||||
'\nsettings:',
|
|
||||||
settings
|
|
||||||
);
|
|
||||||
this.active = this.getDefaultSettings();
|
|
||||||
this.active.version = this.version;
|
|
||||||
await this.save();
|
|
||||||
return this.active;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if there's settings, set saved object as active settings
|
|
||||||
this.active = settings;
|
|
||||||
|
|
||||||
// if last saved settings was for version prior to 4.x, we reset settings to default
|
|
||||||
// it's not like people will notice cos that version didn't preserve settings at all
|
|
||||||
if (this.active.version && !settings.version.startsWith('4')) {
|
|
||||||
this.active = this.getDefaultSettings();
|
|
||||||
this.active.version = this.version;
|
|
||||||
await this.save();
|
|
||||||
return this.active;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// if version number is undefined, we make it defined
|
|
||||||
// this should only happen on first extension initialization
|
|
||||||
if (!this.active.version) {
|
|
||||||
this.active.version = this.version;
|
|
||||||
await this.save();
|
|
||||||
return this.active;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if extension has been updated. If not, return settings as they were retrieved
|
|
||||||
if (this.active.version === this.version) {
|
|
||||||
this.logger.log('info', 'settings', "[Settings::init] extension was saved with current version of ultrawidify. Returning object as-is.");
|
|
||||||
return this.active;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This means extension update happened.
|
|
||||||
// btw fun fact — we can do version rollbacks, which might come in handy while testing
|
|
||||||
this.active.version = this.version;
|
|
||||||
|
|
||||||
// if extension has been updated, update existing settings with any options added in the
|
|
||||||
// new version. In addition to that, we remove old keys that are no longer used.
|
|
||||||
const patched = ObjectCopy.addNew(settings, this.default);
|
|
||||||
this.logger.log('info', 'settings',"[Settings.init] Results from ObjectCopy.addNew()?", patched, "\n\nSettings from storage", settings, "\ndefault?", this.default);
|
|
||||||
|
|
||||||
if (patched) {
|
|
||||||
this.active = patched;
|
|
||||||
}
|
|
||||||
|
|
||||||
// in case settings in previous version contained a fucky wucky, we overwrite existing settings with a patch
|
|
||||||
this.applySettingsPatches(oldVersion, ExtensionConfPatch);
|
|
||||||
|
|
||||||
// set 'whatsNewChecked' flag to false when updating, always
|
|
||||||
this.active.whatsNewChecked = false;
|
|
||||||
// update settings version to current
|
|
||||||
this.active.version = this.version;
|
|
||||||
|
|
||||||
await this.save();
|
|
||||||
return this.active;
|
|
||||||
}
|
|
||||||
|
|
||||||
async get() {
|
|
||||||
let ret;
|
|
||||||
|
|
||||||
if (currentBrowser.firefox) {
|
|
||||||
ret = await browser.storage.local.get('uwSettings');
|
|
||||||
} else if (currentBrowser.chrome) {
|
|
||||||
ret = await new Promise( (resolve, reject) => {
|
|
||||||
chrome.storage.local.get('uwSettings', (res) => resolve(res));
|
|
||||||
});
|
|
||||||
} else if (currentBrowser.edge) {
|
|
||||||
ret = await new Promise( (resolve, reject) => {
|
|
||||||
browser.storage.local.get('uwSettings', (res) => resolve(res));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.log('info', 'settings', 'Got settings:', ret && ret.uwSettings && JSON.parse(ret.uwSettings));
|
|
||||||
|
|
||||||
try {
|
|
||||||
return JSON.parse(ret.uwSettings);
|
|
||||||
} catch(e) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async set(extensionConf) {
|
|
||||||
extensionConf.version = this.version;
|
|
||||||
|
|
||||||
this.logger.log('info', 'settings', "[Settings::set] setting new settings:", extensionConf)
|
|
||||||
|
|
||||||
if (currentBrowser.firefox || currentBrowser.edge) {
|
|
||||||
return browser.storage.local.set( {'uwSettings': JSON.stringify(extensionConf)});
|
|
||||||
} else if (currentBrowser.chrome) {
|
|
||||||
return chrome.storage.local.set( {'uwSettings': JSON.stringify(extensionConf)});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async setActive(activeSettings) {
|
|
||||||
this.active = activeSettings;
|
|
||||||
}
|
|
||||||
|
|
||||||
async setProp(prop, value) {
|
|
||||||
this.active[prop] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
async save() {
|
|
||||||
if (Debug.debug && Debug.storage) {
|
|
||||||
console.log("[Settings::save] Saving active settings:", this.active);
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.set(this.active);
|
|
||||||
}
|
|
||||||
|
|
||||||
async rollback() {
|
|
||||||
this.active = await this.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
getDefaultSettings() {
|
|
||||||
return JSON.parse(JSON.stringify(this.default));
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
// Nastavitve za posamezno stran
|
|
||||||
// Config for a given page:
|
|
||||||
//
|
|
||||||
// <hostname> : {
|
|
||||||
// status: <option> // should extension work on this site?
|
|
||||||
// arStatus: <option> // should we do autodetection on this site?
|
|
||||||
// statusEmbedded: <option> // reserved for future... maybe
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// Veljavne vrednosti za možnosti
|
|
||||||
// Valid values for options:
|
|
||||||
//
|
|
||||||
// status, arStatus, statusEmbedded:
|
|
||||||
//
|
|
||||||
// * enabled — always allow
|
|
||||||
// * basic — only allow fullscreen
|
|
||||||
// * default — allow if default is to allow, block if default is to block
|
|
||||||
// * disabled — never allow
|
|
||||||
|
|
||||||
|
|
||||||
getActionsForSite(site) {
|
|
||||||
if (!site) {
|
|
||||||
return this.active.actions;
|
|
||||||
}
|
|
||||||
if (this.active.sites[site] && this.active.sites[site].actions && this.active.sites[site].actions.length > 0) {
|
|
||||||
return this.active.sites[site].actions;
|
|
||||||
}
|
|
||||||
return this.active.actions;
|
|
||||||
}
|
|
||||||
|
|
||||||
getExtensionMode(site) {
|
|
||||||
if (!site) {
|
|
||||||
site = window.location.hostname;
|
|
||||||
|
|
||||||
if (!site) {
|
|
||||||
this.logger.log('info', 'settings', `[Settings::canStartExtension] window.location.hostname is null or undefined: ${window.location.hostname} \nactive settings:`, this.active);
|
|
||||||
return ExtensionMode.Disabled;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// if site-specific settings don't exist for the site, we use default mode:
|
|
||||||
if (! this.active.sites[site]) {
|
|
||||||
return this.getExtensionMode('@global');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.active.sites[site].mode === ExtensionMode.Enabled) {
|
|
||||||
return ExtensionMode.Enabled;
|
|
||||||
} else if (this.active.sites[site].mode === ExtensionMode.Basic) {
|
|
||||||
return ExtensionMode.Basic;
|
|
||||||
} else if (this.active.sites[site].mode === ExtensionMode.Default && site !== '@global') {
|
|
||||||
return this.getExtensionMode('@global');
|
|
||||||
} else {
|
|
||||||
return ExtensionMode.Disabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch(e){
|
|
||||||
this.logger.log('error', 'settings', "[Settings.js::canStartExtension] Something went wrong — are settings defined/has init() been called?\n\nerror:", e, "\n\nSettings object:", this)
|
|
||||||
|
|
||||||
return ExtensionMode.Disabled;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
canStartExtension(site) {
|
|
||||||
// returns 'true' if extension can be started on a given site. Returns false if we shouldn't run.
|
|
||||||
if (!site) {
|
|
||||||
site = window.location.hostname;
|
|
||||||
|
|
||||||
if (!site) {
|
|
||||||
this.logger.log('info', 'settings', `[Settings::canStartExtension] window.location.hostname is null or undefined: ${window.location.hostname} \nactive settings:`, this.active);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if (Debug.debug) {
|
|
||||||
// // let's just temporarily disable debugging while recursively calling
|
|
||||||
// // this function to get extension status on current site without duplo
|
|
||||||
// // console logs (and without endless recursion)
|
|
||||||
// Debug.debug = false;
|
|
||||||
// const cse = this.canStartExtension(site);
|
|
||||||
// Debug.debug = true;
|
|
||||||
// }
|
|
||||||
try{
|
|
||||||
// if site is not defined, we use default mode:
|
|
||||||
if (! this.active.sites[site] || this.active.sites[site].mode === ExtensionMode.Default) {
|
|
||||||
return this.active.sites['@global'].mode === ExtensionMode.Enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.active.sites['@global'].mode === ExtensionMode.Enabled) {
|
|
||||||
return this.active.sites[site].mode !== ExtensionMode.Disabled;
|
|
||||||
} else if (this.active.sites['@global'].mode === ExtensionMode.Whitelist) {
|
|
||||||
return this.active.sites[site].mode === ExtensionMode.Enabled;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} catch(e) {
|
|
||||||
this.logger.log('error', 'settings', "[Settings.js::canStartExtension] Something went wrong — are settings defined/has init() been called?\nSettings object:", this);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
keyboardShortcutsEnabled(site) {
|
|
||||||
if (!site) {
|
|
||||||
site = window.location.hostname;
|
|
||||||
}
|
|
||||||
if (!site) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (!this.active.sites[site]
|
|
||||||
|| this.active.sites[site].keyboardShortcutsEnabled === undefined
|
|
||||||
|| this.active.sites[site].keyboardShortcutsEnabled === ExtensionMode.Default) {
|
|
||||||
return this.keyboardShortcutsEnabled('@global');
|
|
||||||
} else {
|
|
||||||
return this.active.sites[site].keyboardShortcutsEnabled === ExtensionMode.Enabled;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
this.logger.log('info', 'settings',"[Settings.js::keyboardDisabled] something went wrong:", e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extensionEnabled(){
|
|
||||||
return this.active.sites['@global'] !== ExtensionMode.Disabled
|
|
||||||
}
|
|
||||||
|
|
||||||
extensionEnabledForSite(site) {
|
|
||||||
return this.canStartExtension(site);
|
|
||||||
}
|
|
||||||
|
|
||||||
canStartAutoAr(site) {
|
|
||||||
// 'site' argument is only ever used when calling this function recursively for debugging
|
|
||||||
if (!site) {
|
|
||||||
site = window.location.host;
|
|
||||||
|
|
||||||
if (!site) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Debug.debug) {
|
|
||||||
// let's just temporarily disable debugging while recursively calling
|
|
||||||
// this function to get extension status on current site without duplo
|
|
||||||
// console logs (and without endless recursion)
|
|
||||||
Debug.debug = false;
|
|
||||||
const csar = this.canStartAutoAr(site);
|
|
||||||
Debug.debug = true;
|
|
||||||
|
|
||||||
this.logger.log('info', 'settings', "[Settings::canStartAutoAr] ----------------\nCAN WE START AUTOAR ON SITE", site,
|
|
||||||
"?\n\nsettings.active.sites[site]=", this.active.sites[site], "settings.active.sites[@global]=", this.active.sites['@global'],
|
|
||||||
"\nAutoar mode (global)?", this.active.sites['@global'].autoar,
|
|
||||||
`\nAutoar mode (${site})`, this.active.sites[site] ? this.active.sites[site].autoar : '<not defined>',
|
|
||||||
"\nCan autoar be started?", csar
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if site is not defined, we use default mode:
|
|
||||||
if (! this.active.sites[site]) {
|
|
||||||
return this.active.sites['@global'].autoar === ExtensionMode.Enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.active.sites['@global'].autoar === ExtensionMode.Enabled) {
|
|
||||||
return this.active.sites[site].autoar !== ExtensionMode.Disabled;
|
|
||||||
} else if (this.active.sites['@global'].autoar === ExtensionMode.Whitelist) {
|
|
||||||
this.logger.log('info', 'settings', "canStartAutoAr — can(not) start csar because extension is in whitelist mode, and this site is (not) equal to", ExtensionMode.Enabled)
|
|
||||||
return this.active.sites[site].autoar === ExtensionMode.Enabled;
|
|
||||||
} else {
|
|
||||||
this.logger.log('info', 'settings', "canStartAutoAr — cannot start csar because extension is globally disabled")
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getDefaultOption(option) {
|
|
||||||
const allDefault = {
|
|
||||||
mode: ExtensionMode.Default,
|
|
||||||
autoar: ExtensionMode.Default,
|
|
||||||
autoarFallback: ExtensionMode.Default,
|
|
||||||
stretch: Stretch.Default,
|
|
||||||
videoAlignment: VideoAlignment.Default,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!option || allDefault[option] === undefined) {
|
|
||||||
return allDefault;
|
|
||||||
}
|
|
||||||
|
|
||||||
return allDefault[option];
|
|
||||||
}
|
|
||||||
|
|
||||||
getDefaultAr(site) {
|
|
||||||
// site = this.getSiteSettings(site);
|
|
||||||
|
|
||||||
// if (site.defaultAr) {
|
|
||||||
// return site.defaultAr;
|
|
||||||
// }
|
|
||||||
return this.active.miscSettings.defaultAr;
|
|
||||||
}
|
|
||||||
|
|
||||||
getDefaultStretchMode(site) {
|
|
||||||
if (site && this.active.sites[site] && this.active.sites[site].stretch !== Stretch.Default) {
|
|
||||||
return this.active.sites[site].stretch;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.active.sites['@global'].stretch;
|
|
||||||
}
|
|
||||||
|
|
||||||
getDefaultVideoAlignment(site) {
|
|
||||||
if (site && this.active.sites[site] && this.active.sites[site].videoAlignment !== VideoAlignment.Default) {
|
|
||||||
return this.active.sites[site].videoAlignment;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.active.sites['@global'].videoAlignment;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Settings;
|
|
||||||
@ -1,897 +0,0 @@
|
|||||||
|
|
||||||
import Debug from '../../conf/Debug';
|
|
||||||
import EdgeDetect from './edge-detect/EdgeDetect';
|
|
||||||
import EdgeStatus from './edge-detect/enums/EdgeStatusEnum';
|
|
||||||
import EdgeDetectPrimaryDirection from './edge-detect/enums/EdgeDetectPrimaryDirectionEnum';
|
|
||||||
import EdgeDetectQuality from './edge-detect/enums/EdgeDetectQualityEnum';
|
|
||||||
import GuardLine from './GuardLine';
|
|
||||||
import DebugCanvas from './DebugCanvas';
|
|
||||||
import VideoAlignment from '../../../common/enums/video-alignment.enum';
|
|
||||||
import AspectRatio from '../../../common/enums/aspect-ratio.enum';
|
|
||||||
|
|
||||||
class ArDetector {
|
|
||||||
|
|
||||||
constructor(videoData){
|
|
||||||
this.logger = videoData.logger;
|
|
||||||
this.conf = videoData;
|
|
||||||
this.video = videoData.video;
|
|
||||||
this.settings = videoData.settings;
|
|
||||||
|
|
||||||
this.setupTimer = null;
|
|
||||||
|
|
||||||
this.sampleCols = [];
|
|
||||||
|
|
||||||
this.canFallback = true;
|
|
||||||
this.fallbackMode = false;
|
|
||||||
|
|
||||||
this.blackLevel = this.settings.active.arDetect.blackbar.blackLevel;
|
|
||||||
|
|
||||||
this.arid = (Math.random()*100).toFixed();
|
|
||||||
|
|
||||||
// ar detector starts in this state. running main() sets both to false
|
|
||||||
this._halted = true;
|
|
||||||
this._exited = true;
|
|
||||||
|
|
||||||
// we can tick manually, for debugging
|
|
||||||
this._manualTicks = false;
|
|
||||||
this._nextTick = false;
|
|
||||||
|
|
||||||
this.canDoFallbackMode = false;
|
|
||||||
this.logger.log('info', 'init', `[ArDetector::ctor] creating new ArDetector. arid: ${this.arid}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
setManualTick(manualTick) {
|
|
||||||
this._manualTicks = manualTick;
|
|
||||||
}
|
|
||||||
|
|
||||||
tick() {
|
|
||||||
this._nextTick = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
init(){
|
|
||||||
this.logger.log('info', 'init', `[ArDetect::init] <@${this.arid}> Initializing autodetection.`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (this.settings.canStartAutoAr()) {
|
|
||||||
this.setup();
|
|
||||||
} else {
|
|
||||||
throw "Settings prevent autoar from starting"
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
this.logger.log('error', 'init', `%c[ArDetect::init] <@${this.arid}> Initialization failed.`, _ard_console_stop, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy(){
|
|
||||||
this.logger.log('info', 'init', `%c[ArDetect::destroy] <@${this.arid}> Destroying aard.`, _ard_console_stop, e);
|
|
||||||
// this.debugCanvas.destroy();
|
|
||||||
this.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
setup(cwidth, cheight){
|
|
||||||
this.logger.log('info', 'init', `[ArDetect::setup] <@${this.arid}> Starting autodetection setup.`);
|
|
||||||
//
|
|
||||||
// [-1] check for zero-width and zero-height videos. If we detect this, we kick the proverbial
|
|
||||||
// can some distance down the road. This problem will prolly fix itself soon. We'll also
|
|
||||||
// not do any other setup until this issue is fixed
|
|
||||||
//
|
|
||||||
if(this.video.videoWidth === 0 || this.video.videoHeight === 0 ){
|
|
||||||
this.logger.log('warn', 'debug', `[ArDetect::setup] <@${this.arid}> This video has zero width or zero height. Dimensions: ${this.video.videoWidth} × ${this.video.videoHeight}`);
|
|
||||||
|
|
||||||
this.scheduleInitRestart();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// [0] initiate "dependencies" first
|
|
||||||
//
|
|
||||||
|
|
||||||
this.guardLine = new GuardLine(this);
|
|
||||||
this.edgeDetector = new EdgeDetect(this);
|
|
||||||
// this.debugCanvas = new DebugCanvas(this);
|
|
||||||
|
|
||||||
|
|
||||||
//
|
|
||||||
// [1] initiate canvases
|
|
||||||
//
|
|
||||||
|
|
||||||
if (!cwidth) {
|
|
||||||
cwidth = this.settings.active.arDetect.canvasDimensions.sampleCanvas.width;
|
|
||||||
cheight = this.settings.active.arDetect.canvasDimensions.sampleCanvas.height;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.canvas) {
|
|
||||||
this.canvas.remove();
|
|
||||||
}
|
|
||||||
if (this.blackframeCanvas) {
|
|
||||||
this.blackframeCanvas.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
// things to note: we'll be keeping canvas in memory only.
|
|
||||||
this.canvas = document.createElement("canvas");
|
|
||||||
this.canvas.width = cwidth;
|
|
||||||
this.canvas.height = cheight;
|
|
||||||
this.blackframeCanvas = document.createElement("canvas");
|
|
||||||
this.blackframeCanvas.width = this.settings.active.arDetect.canvasDimensions.blackframeCanvas.width;
|
|
||||||
this.blackframeCanvas.height = this.settings.active.arDetect.canvasDimensions.blackframeCanvas.height;
|
|
||||||
|
|
||||||
this.context = this.canvas.getContext("2d");
|
|
||||||
this.blackframeContext = this.blackframeCanvas.getContext("2d");
|
|
||||||
|
|
||||||
|
|
||||||
// do setup once
|
|
||||||
// tho we could do it for every frame
|
|
||||||
this.canvasScaleFactor = cheight / this.video.videoHeight;
|
|
||||||
|
|
||||||
|
|
||||||
//
|
|
||||||
// [2] determine places we'll use to sample our main frame
|
|
||||||
//
|
|
||||||
|
|
||||||
var ncol = this.settings.active.arDetect.sampling.staticCols;
|
|
||||||
var nrow = this.settings.active.arDetect.sampling.staticRows;
|
|
||||||
|
|
||||||
var colSpacing = this.canvas.width / ncol;
|
|
||||||
var rowSpacing = (this.canvas.height << 2) / nrow;
|
|
||||||
|
|
||||||
this.sampleLines = [];
|
|
||||||
this.sampleCols = [];
|
|
||||||
|
|
||||||
for(var i = 0; i < ncol; i++){
|
|
||||||
if(i < ncol - 1)
|
|
||||||
this.sampleCols.push(Math.round(colSpacing * i));
|
|
||||||
else{
|
|
||||||
this.sampleCols.push(Math.round(colSpacing * i) - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for(var i = 0; i < nrow; i++){
|
|
||||||
if(i < ncol - 5)
|
|
||||||
this.sampleLines.push(Math.round(rowSpacing * i));
|
|
||||||
else{
|
|
||||||
this.sampleLines.push(Math.round(rowSpacing * i) - 4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// [3] detect if we're in the fallback mode and reset guardline
|
|
||||||
//
|
|
||||||
|
|
||||||
if (this.fallbackMode) {
|
|
||||||
this.logger.log('warn', 'debug', `[ArDetect::setup] <@${this.arid}> WARNING: CANVAS RESET DETECTED/we're in fallback mode - recalculating guardLine`, "background: #000; color: #ff2");
|
|
||||||
// blackbar, imagebar
|
|
||||||
this.guardLine.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// [4] see if browser supports "fallback mode" by drawing a small portion of our window
|
|
||||||
//
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.blackframeContext.drawWindow(window,0, 0, this.blackframeCanvas.width, this.blackframeCanvas.height, "rgba(0,0,128,1)");
|
|
||||||
this.canDoFallbackMode = true;
|
|
||||||
} catch (e) {
|
|
||||||
this.canDoFallbackMode = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// [5] do other things setup needs to do
|
|
||||||
//
|
|
||||||
|
|
||||||
this.detectionTimeoutEventCount = 0;
|
|
||||||
this.resetBlackLevel();
|
|
||||||
|
|
||||||
// if we're restarting ArDetect, we need to do this in order to force-recalculate aspect ratio
|
|
||||||
this.conf.resizer.setLastAr({type: AspectRatio.Automatic, ratio: this.getDefaultAr()});
|
|
||||||
|
|
||||||
this.canvasImageDataRowLength = cwidth << 2;
|
|
||||||
this.noLetterboxCanvasReset = false;
|
|
||||||
|
|
||||||
if (this.settings.canStartAutoAr() ) {
|
|
||||||
this.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(Debug.debugCanvas.enabled){
|
|
||||||
// this.debugCanvas.init({width: cwidth, height: cheight});
|
|
||||||
// DebugCanvas.draw("test marker","test","rect", {x:5, y:5}, {width: 5, height: 5});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.conf.arSetupComplete = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
start() {
|
|
||||||
this.logger.log('info', 'debug', `"%c[ArDetect::start] <@${this.arid}> Starting automatic aspect ratio detection`, _ard_console_start);
|
|
||||||
|
|
||||||
if (this.conf.resizer.lastAr.type === AspectRatio.Automatic) {
|
|
||||||
// ensure first autodetection will run in any case
|
|
||||||
this.conf.resizer.setLastAr({type: AspectRatio.Automatic, ratio: this.getDefaultAr()});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// launch main() if it's currently not running:
|
|
||||||
this.main();
|
|
||||||
// automatic detection starts halted. If halted=false when main first starts, extension won't run
|
|
||||||
// this._paused is undefined the first time we run this function, which is effectively the same thing
|
|
||||||
// as false. Still, we'll explicitly fix this here.
|
|
||||||
this._paused = false;
|
|
||||||
this._halted = false;
|
|
||||||
this._paused = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
unpause() {
|
|
||||||
// pause only if we were running before. Don't pause if we aren't running
|
|
||||||
// (we are running when _halted is neither true nor undefined)
|
|
||||||
if (this._paused && this._halted === false) {
|
|
||||||
this._paused = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pause() {
|
|
||||||
// pause only if we were running before. Don't pause if we aren't running
|
|
||||||
// (we are running when _halted is neither true nor undefined)
|
|
||||||
if (this._halted === false) {
|
|
||||||
this._paused = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stop(){
|
|
||||||
this.logger.log('info', 'debug', `"%c[ArDetect::stop] <@${this.arid}> Stopping automatic aspect ratio detection`, _ard_console_stop);
|
|
||||||
this._halted = true;
|
|
||||||
// this.conf.resizer.setArLastAr();
|
|
||||||
}
|
|
||||||
|
|
||||||
async main() {
|
|
||||||
if (this._paused) {
|
|
||||||
// unpause if paused
|
|
||||||
this._paused = false;
|
|
||||||
return; // main loop still keeps executing. Return is needed to avoid a million instances of autodetection
|
|
||||||
}
|
|
||||||
if (!this._halted) {
|
|
||||||
// we are already running, don't run twice
|
|
||||||
// this would have handled the 'paused' from before, actually.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let exitedRetries = 10;
|
|
||||||
|
|
||||||
while (!this._exited && exitedRetries --> 0) {
|
|
||||||
this.logger.log('warn', 'debug', `[ArDetect::main] <@${this.arid}> We are trying to start another instance of autodetection on current video, but the previous instance hasn't exited yet. Waiting for old instance to exit ...`);
|
|
||||||
await this.sleep(this.settings.active.arDetect.timers.tickrate);
|
|
||||||
}
|
|
||||||
if (!this._exited) {
|
|
||||||
this.logger.log('error', 'debug', `[ArDetect::main] <@${this.arid}> Previous instance didn't exit in time. Not starting a new one.`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.log('info', 'debug', `%c[ArDetect::main] <@${this.arid}> Previous instance didn't exit in time. Not starting a new one.`);
|
|
||||||
|
|
||||||
// we need to unhalt:
|
|
||||||
this._halted = false;
|
|
||||||
this._exited = false;
|
|
||||||
|
|
||||||
// set initial timestamps so frame check will trigger the first time we run the loop
|
|
||||||
let lastFrameCheckStartTime = Date.now() - (this.settings.active.arDetect.timers.playing << 1);
|
|
||||||
|
|
||||||
const frameCheckTimes = new Array(10).fill(-1);
|
|
||||||
let frameCheckBufferIndex = 0;
|
|
||||||
let fcstart, fctime;
|
|
||||||
|
|
||||||
while (this && !this._halted) {
|
|
||||||
// NOTE: we separated tickrate and inter-check timeouts so that when video switches
|
|
||||||
// state from 'paused' to 'playing', we don't need to wait for the rest of the longer
|
|
||||||
// paused state timeout to finish.
|
|
||||||
|
|
||||||
if ( (!this._manualTicks && this.canTriggerFrameCheck(lastFrameCheckStartTime)) || this._nextTick) {
|
|
||||||
this._nextTick = false;
|
|
||||||
|
|
||||||
lastFrameCheckStartTime = Date.now();
|
|
||||||
fcstart = performance.now();
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.frameCheck();
|
|
||||||
} catch (e) {
|
|
||||||
this.logger.log('error', 'debug', `%c[ArDetect::main] <@${this.arid}> Frame check failed:`, "color: #000, background: #f00", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Debug.performanceMetrics) {
|
|
||||||
fctime = performance.now() - fcstart;
|
|
||||||
frameCheckTimes[frameCheckBufferIndex % frameCheckTimes.length] = fctime;
|
|
||||||
this.conf.pageInfo.sendPerformanceUpdate({frameCheckTimes: frameCheckTimes, lastFrameCheckTime: fctime});
|
|
||||||
++frameCheckBufferIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.sleep(this.settings.active.arDetect.timers.tickrate);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.log('info', 'debug', `%c[ArDetect::main] <@${this.arid}> Main autodetection loop exited. Halted? ${this._halted}`, _ard_console_stop);
|
|
||||||
this._exited = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async sleep(timeout) {
|
|
||||||
return new Promise( (resolve, reject) => setTimeout(() => resolve(), timeout));
|
|
||||||
}
|
|
||||||
|
|
||||||
canTriggerFrameCheck(lastFrameCheckStartTime) {
|
|
||||||
if (this._paused) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (this.video.ended || this.video.paused){
|
|
||||||
// we slow down if ended or pausing. Detecting is pointless.
|
|
||||||
// we don't stop outright in case seeking happens during pause/after video was
|
|
||||||
// ended and video gets into 'playing' state again
|
|
||||||
return Date.now() - lastFrameCheckStartTime > this.settings.active.arDetect.timers.paused;
|
|
||||||
}
|
|
||||||
if (this.video.error){
|
|
||||||
// če je video pavziran, še vedno skušamo zaznati razmerje stranic - ampak bolj poredko.
|
|
||||||
// if the video is paused, we still do autodetection. We just do it less often.
|
|
||||||
return Date.now() - lastFrameCheckStartTime > this.settings.active.arDetect.timers.error;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Date.now() - lastFrameCheckStartTime > this.settings.active.arDetect.timers.playing;
|
|
||||||
}
|
|
||||||
|
|
||||||
isRunning(){
|
|
||||||
return ! (this._halted || this._paused || this._exited);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
scheduleInitRestart(timeout, force_reset){
|
|
||||||
if(! timeout){
|
|
||||||
timeout = 100;
|
|
||||||
}
|
|
||||||
// don't allow more than 1 instance
|
|
||||||
if(this.setupTimer){
|
|
||||||
clearTimeout(this.setupTimer);
|
|
||||||
}
|
|
||||||
|
|
||||||
var ths = this;
|
|
||||||
this.setupTimer = setTimeout(function(){
|
|
||||||
ths.setupTimer = null;
|
|
||||||
try{
|
|
||||||
ths.main();
|
|
||||||
} catch(e) {
|
|
||||||
this.logger('error', 'debug', `[ArDetector::scheduleInitRestart] <@${this.arid}> Failed to start main(). Error:`,e);
|
|
||||||
}
|
|
||||||
ths = null;
|
|
||||||
},
|
|
||||||
timeout
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//#region helper functions (general)
|
|
||||||
attachCanvas(canvas){
|
|
||||||
if(this.attachedCanvas)
|
|
||||||
this.attachedCanvas.remove();
|
|
||||||
|
|
||||||
// todo: place canvas on top of the video instead of random location
|
|
||||||
canvas.style.position = "absolute";
|
|
||||||
canvas.style.left = "200px";
|
|
||||||
canvas.style.top = "1200px";
|
|
||||||
canvas.style.zIndex = 10000;
|
|
||||||
|
|
||||||
document.getElementsByTagName("body")[0]
|
|
||||||
.appendChild(canvas);
|
|
||||||
}
|
|
||||||
|
|
||||||
canvasReadyForDrawWindow(){
|
|
||||||
this.logger.log('info', 'debug', `%c[ArDetect::canvasReadyForDrawWindow] <@${this.arid}> canvas is ${this.canvas.height === window.innerHeight ? '' : 'NOT '}ready for drawWindow(). Canvas height: ${this.canvas.height}px; window inner height: ${window.innerHeight}px.`)
|
|
||||||
|
|
||||||
return this.canvas.height == window.innerHeight
|
|
||||||
}
|
|
||||||
|
|
||||||
getTimeout(baseTimeout, startTime){
|
|
||||||
var execTime = (performance.now() - startTime);
|
|
||||||
|
|
||||||
return baseTimeout;
|
|
||||||
}
|
|
||||||
//#endregion
|
|
||||||
|
|
||||||
getDefaultAr() {
|
|
||||||
return this.video.videoWidth / this.video.videoHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
calculateArFromEdges(edges) {
|
|
||||||
// if we don't specify these things, they'll have some default values.
|
|
||||||
if(edges.top === undefined){
|
|
||||||
edges.top = 0;
|
|
||||||
edges.bottom = 0;
|
|
||||||
edges.left = 0; // RESERVED FOR FUTURE — CURRENTLY UNUSED
|
|
||||||
edges.right = 0; // THIS FUNCTION CAN PRESENTLY ONLY HANDLE LETTERBOX
|
|
||||||
}
|
|
||||||
|
|
||||||
let letterbox = edges.top + edges.bottom;
|
|
||||||
|
|
||||||
|
|
||||||
if (! this.fallbackMode) {
|
|
||||||
// Since video is stretched to fit the canvas, we need to take that into account when calculating target
|
|
||||||
// aspect ratio and correct our calculations to account for that
|
|
||||||
|
|
||||||
const fileAr = this.video.videoWidth / this.video.videoHeight;
|
|
||||||
const canvasAr = this.canvas.width / this.canvas.height;
|
|
||||||
let widthCorrected;
|
|
||||||
|
|
||||||
if (edges.top && edges.bottom) {
|
|
||||||
// in case of letterbox, we take canvas height as canon and assume width got stretched or squished
|
|
||||||
|
|
||||||
if (fileAr != canvasAr) {
|
|
||||||
widthCorrected = this.canvas.height * fileAr;
|
|
||||||
} else {
|
|
||||||
widthCorrected = this.canvas.width;
|
|
||||||
}
|
|
||||||
|
|
||||||
return widthCorrected / (this.canvas.height - letterbox);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// fallback mode behaves a wee bit differently
|
|
||||||
|
|
||||||
let zoomFactor = 1;
|
|
||||||
|
|
||||||
// there's stuff missing from the canvas. We need to assume canvas' actual height is bigger by a factor x, where
|
|
||||||
// x = [video.zoomedHeight] / [video.unzoomedHeight]
|
|
||||||
//
|
|
||||||
// letterbox also needs to be corrected:
|
|
||||||
// letterbox += [video.zoomedHeight] - [video.unzoomedHeight]
|
|
||||||
|
|
||||||
var vbr = this.video.getBoundingClientRect();
|
|
||||||
|
|
||||||
zoomFactor = vbr.height / this.video.clientHeight;
|
|
||||||
letterbox += vbr.height - this.video.clientHeight;
|
|
||||||
|
|
||||||
var trueHeight = this.canvas.height * zoomFactor - letterbox;
|
|
||||||
|
|
||||||
if(edges.top > 1 && edges.top <= this.settings.active.arDetect.fallbackMode.noTriggerZonePx ){
|
|
||||||
this.logger.log('info', 'arDetect', `%c[ArDetect::calculateArFromEdges] <@${this.arid}> Edge is in the no-trigger zone. Aspect ratio change is not triggered.`)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// varnostno območje, ki naj ostane črno (da lahko v fallback načinu odkrijemo ožanje razmerja stranic).
|
|
||||||
// x2, ker je safetyBorderPx definiran za eno stran.
|
|
||||||
// safety border so we can detect aspect ratio narrowing (21:9 -> 16:9).
|
|
||||||
// x2 because safetyBorderPx is for one side.
|
|
||||||
trueHeight += (this.settings.active.arDetect.fallbackMode.safetyBorderPx << 1);
|
|
||||||
|
|
||||||
return this.canvas.width * zoomFactor / trueHeight;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
processAr(trueAr){
|
|
||||||
this.detectedAr = trueAr;
|
|
||||||
|
|
||||||
// poglejmo, če se je razmerje stranic spremenilo
|
|
||||||
// check if aspect ratio is changed:
|
|
||||||
var lastAr = this.conf.resizer.getLastAr();
|
|
||||||
if (lastAr.type === AspectRatio.Automatic && lastAr.ratio !== null){
|
|
||||||
// spremembo lahko zavrnemo samo, če uporabljamo avtomatski način delovanja in če smo razmerje stranic
|
|
||||||
// že nastavili.
|
|
||||||
//
|
|
||||||
// we can only deny aspect ratio changes if we use automatic mode and if aspect ratio was set from here.
|
|
||||||
|
|
||||||
var arDiff = trueAr - lastAr.ar;
|
|
||||||
|
|
||||||
if (arDiff < 0)
|
|
||||||
arDiff = -arDiff;
|
|
||||||
|
|
||||||
var arDiff_percent = arDiff / trueAr;
|
|
||||||
|
|
||||||
// ali je sprememba v mejah dovoljenega? Če da -> fertik
|
|
||||||
// is ar variance within acceptable levels? If yes -> we done
|
|
||||||
this.logger.log('info', 'arDetect', `%c[ArDetect::processAr] <@${this.arid}> New aspect ratio varies from the old one by this much:\n`,"color: #aaf","old Ar", lastAr.ar, "current ar", trueAr, "arDiff (absolute):",arDiff,"ar diff (relative to new ar)", arDiff_percent);
|
|
||||||
|
|
||||||
if (arDiff < trueAr * this.settings.active.arDetect.allowedArVariance){
|
|
||||||
this.logger.log('info', 'arDetect', `%c[ArDetect::processAr] <@${this.arid}> Aspect ratio change denied — diff %: ${arDiff_percent}`, "background: #740; color: #fa2");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.logger.log('info', 'arDetect', `%c[ArDetect::processAr] <@${this.arid}> aspect ratio change accepted — diff %: ${arDiff_percent}`, "background: #153; color: #4f9");
|
|
||||||
}
|
|
||||||
this.logger.log('info', 'debug', `%c[ArDetect::processAr] <@${this.arid}> Triggering aspect ratio change. New aspect ratio: ${trueAr}`, _ard_console_change);
|
|
||||||
|
|
||||||
this.conf.resizer.setAr({type: AspectRatio.Automatic, ratio: trueAr}, {type: AspectRatio.Automatic, ratio: trueAr});
|
|
||||||
}
|
|
||||||
|
|
||||||
frameCheck(){
|
|
||||||
if(! this.video){
|
|
||||||
this.logger.log('error', 'debug', `%c[ArDetect::frameCheck] <@${this.arid}> Video went missing. Destroying current instance of videoData.`);
|
|
||||||
this.conf.destroy();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.blackframeContext) {
|
|
||||||
this.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
var startTime = performance.now();
|
|
||||||
let sampleCols = this.sampleCols.slice(0);
|
|
||||||
|
|
||||||
//
|
|
||||||
// [0] blackframe tests (they also determine whether we need fallback mode)
|
|
||||||
//
|
|
||||||
try {
|
|
||||||
this.blackframeContext.drawImage(this.video, 0, 0, this.blackframeCanvas.width, this.blackframeCanvas.height);
|
|
||||||
this.fallbackMode = false;
|
|
||||||
} catch (e) {
|
|
||||||
this.logger.log('error', 'arDetect', `%c[ArDetect::frameCheck] <@${this.arid}> %c[ArDetect::frameCheck] can't draw image on canvas. ${this.canDoFallbackMode ? 'Trying canvas.drawWindow instead' : 'Doing nothing as browser doesn\'t support fallback mode.'}`, "color:#000; backgroud:#f51;", e);
|
|
||||||
|
|
||||||
// nothing to see here, really, if fallback mode isn't supported by browser
|
|
||||||
if (! this.canDoFallbackMode) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (! this.canvasReadyForDrawWindow()) {
|
|
||||||
// this means canvas needs to be resized, so we'll just re-run setup with all those new parameters
|
|
||||||
this.stop();
|
|
||||||
|
|
||||||
let newCanvasWidth = window.innerHeight * (this.video.videoWidth / this.video.videoHeight);
|
|
||||||
let newCanvasHeight = window.innerHeight;
|
|
||||||
|
|
||||||
if (this.conf.resizer.videoAlignment === VideoAlignment.Center) {
|
|
||||||
this.canvasDrawWindowHOffset = Math.round((window.innerWidth - newCanvasWidth) * 0.5);
|
|
||||||
} else if (this.conf.resizer.videoAlignment === VideoAlignment.Left) {
|
|
||||||
this.canvasDrawWindowHOffset = 0;
|
|
||||||
} else {
|
|
||||||
this.canvasDrawWindowHOffset = window.innerWidth - newCanvasWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setup(newCanvasWidth, newCanvasHeight);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// if this is the case, we'll first draw on canvas, as we'll need intermediate canvas if we want to get a
|
|
||||||
// smaller sample for blackframe check
|
|
||||||
this.fallbackMode = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.context.drawWindow(window, this.canvasDrawWindowHOffset, 0, this.canvas.width, this.canvas.height, "rgba(0,0,128,1)");
|
|
||||||
} catch (e) {
|
|
||||||
this.logger.log('error', 'arDetect', `%c[ArDetect::frameCheck] can't draw image on canvas with fallback mode either. This error is prolly only temporary.`, "color:#000; backgroud:#f51;", e);
|
|
||||||
return; // it's prolly just a fluke, so we do nothing special here
|
|
||||||
}
|
|
||||||
// draw blackframe sample from our main sample:
|
|
||||||
this.blackframeContext.drawImage(this.canvas, this.blackframeCanvas.width, this.blackframeCanvas.height)
|
|
||||||
|
|
||||||
this.logger.log('info', 'arDetect_verbose', `%c[ArDetect::frameCheck] canvas.drawImage seems to have worked`, "color:#000; backgroud:#2f5;");
|
|
||||||
}
|
|
||||||
|
|
||||||
const bfanalysis = this.blackframeTest();
|
|
||||||
if (bfanalysis.isBlack) {
|
|
||||||
// we don't do any corrections on frames confirmed black
|
|
||||||
this.logger.log('info', 'arDetect_verbose', `%c[ArDetect::frameCheck] Black frame analysis suggests this frame is black or too dark. Doing nothing.`, "color: #fa3", bfanalysis);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// if we are in fallback mode, then frame has already been drawn to the main canvas.
|
|
||||||
// if we are in normal mode though, the frame has yet to be drawn
|
|
||||||
if (!this.fallbackMode) {
|
|
||||||
this.context.drawImage(this.video, 0, 0, this.canvas.width, this.canvas.height);
|
|
||||||
}
|
|
||||||
const imageData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height).data;
|
|
||||||
|
|
||||||
if (! this.fastLetterboxPresenceTest(imageData, sampleCols) ) {
|
|
||||||
// Če ne zaznamo letterboxa, kličemo reset. Lahko, da je bilo razmerje stranic popravljeno na roke. Možno je tudi,
|
|
||||||
// da je letterbox izginil.
|
|
||||||
// If we don't detect letterbox, we reset aspect ratio to aspect ratio of the video file. The aspect ratio could
|
|
||||||
// have been corrected manually. It's also possible that letterbox (that was there before) disappeared.
|
|
||||||
this.conf.resizer.setAr({type: AspectRatio.Automatic, ratio: this.getDefaultAr()});
|
|
||||||
this.guardLine.reset();
|
|
||||||
this.noLetterboxCanvasReset = true;
|
|
||||||
|
|
||||||
this.logger.log('info', 'arDetect_verbose', `%c[ArDetect::frameCheck] Letterbox not detected in fast test. Letterbox is either gone or we manually corrected aspect ratio. Nothing will be done.`, "color: #fa3");
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Če preverjamo naprej, potem moramo postaviti to vrednost nazaj na 'false'. V nasprotnem primeru se bo
|
|
||||||
// css resetiral enkrat na video/pageload namesto vsakič, ko so za nekaj časa obrobe odstranejene
|
|
||||||
// if we look further we need to reset this value back to false. Otherwise we'll only get CSS reset once
|
|
||||||
// per video/pageload instead of every time letterbox goes away (this can happen more than once per vid)
|
|
||||||
this.noLetterboxCanvasReset = false;
|
|
||||||
|
|
||||||
// poglejmo, če obrežemo preveč.
|
|
||||||
// let's check if we're cropping too much
|
|
||||||
const guardLineOut = this.guardLine.check(imageData, this.fallbackMode);
|
|
||||||
|
|
||||||
// če ni padla nobena izmed funkcij, potem se razmerje stranic ni spremenilo
|
|
||||||
// if both succeed, then aspect ratio hasn't changed.
|
|
||||||
if (!guardLineOut.imageFail && !guardLineOut.blackbarFail) {
|
|
||||||
this.logger.log('info', 'arDetect_verbose', `%c[ArDetect::frameCheck] guardLine tests were successful. (no imagefail and no blackbarfail)\n`, "color: #afa", guardLineOut);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// drugače nadaljujemo, našemu vzorcu stolpcev pa dodamo tiste stolpce, ki so
|
|
||||||
// kršili blackbar (če obstajajo) ter jih razvrstimo
|
|
||||||
// otherwise we continue. We add blackbar violations to the list of the cols
|
|
||||||
// we'll sample and sort them
|
|
||||||
if (guardLineOut.blackbarFail) {
|
|
||||||
sampleCols.concat(guardLineOut.offenders).sort((a, b) => a > b);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we're in fallback mode and blackbar test failed, we restore CSS and quit
|
|
||||||
// (since the new letterbox edge isn't present in our sample due to technical
|
|
||||||
// limitations)
|
|
||||||
if (this.fallbackMode && guardLineOut.blackbarFail) {
|
|
||||||
this.conf.resizer.setAr({type: AspectRatio.Automatic, ratio: this.getDefaultAr()});
|
|
||||||
this.guardLine.reset();
|
|
||||||
this.noLetterboxCanvasReset = true;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// će se razmerje stranic spreminja iz ožjega na širšega, potem najprej poglejmo za prisotnostjo navpičnih črnih obrob.
|
|
||||||
// če so prisotne navpične obrobe tudi na levi in desni strani, potlej obstaja možnost, da gre za logo na črnem ozadju.
|
|
||||||
// v tem primeru obstaja nevarnost, da porežemo preveč. Ker obstaja dovolj velika možnost, da bi porezali preveč, rajši
|
|
||||||
// ne naredimo ničesar.
|
|
||||||
//
|
|
||||||
// če je pillarbox zaznan v primeru spremembe iz ožjega na širše razmerje stranice, razmerje povrnemo na privzeto vrednost.
|
|
||||||
//
|
|
||||||
// If aspect ratio changes from narrower to wider, we first check for presence of pillarbox. Presence of pillarbox indicates
|
|
||||||
// a chance of a logo on black background. We could cut easily cut too much. Because there's a somewhat significant chance
|
|
||||||
// that we will cut too much, we rather avoid doing anything at all. There's gonna be a next chance.
|
|
||||||
try{
|
|
||||||
if(guardLineOut.blackbarFail || guardLineOut.imageFail){
|
|
||||||
if(this.edgeDetector.findBars(imageData, null, EdgeDetectPrimaryDirection.HORIZONTAL).status === 'ar_known'){
|
|
||||||
|
|
||||||
|
|
||||||
if(guardLineOut.blackbarFail){
|
|
||||||
this.logger.log('info', 'arDetect', `[ArDetect::frameCheck] Detected blackbar violation and pillarbox. Resetting to default aspect ratio.`);
|
|
||||||
this.conf.resizer.setAr({type: AspectRatio.Automatic, ratio: this.getDefaultAr()});
|
|
||||||
this.guardLine.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
triggerTimeout = this.getTimeout(baseTimeout, startTime);
|
|
||||||
this.scheduleFrameCheck(triggerTimeout);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch(e) {
|
|
||||||
this.logger.log('info', 'arDetect', `[ArDetect::frameCheck] something went wrong while checking for pillarbox. Error:\n`, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// pa poglejmo, kje se končajo črne letvice na vrhu in na dnu videa.
|
|
||||||
// let's see where black bars end.
|
|
||||||
this.sampleCols_current = sampleCols.length;
|
|
||||||
|
|
||||||
// blackSamples -> {res_top, res_bottom}
|
|
||||||
|
|
||||||
var edgePost = this.edgeDetector.findBars(imageData, sampleCols, EdgeDetectPrimaryDirection.VERTICAL, EdgeDetectQuality.IMPROVED, guardLineOut, bfanalysis);
|
|
||||||
|
|
||||||
this.logger.log('info', 'arDetect_verbose', `%c[ArDetect::frameCheck] edgeDetector returned this\n`, "color: #aaf", edgePost);
|
|
||||||
|
|
||||||
if (edgePost.status !== EdgeStatus.AR_KNOWN){
|
|
||||||
// rob ni bil zaznan, zato ne naredimo ničesar.
|
|
||||||
// no edge was detected. Let's leave things as they were
|
|
||||||
this.logger.log('info', 'arDetect_verbose', `%c[ArDetect::frameCheck] Edge wasn't detected with findBars`, "color: #fa3", edgePost, "EdgeStatus.AR_KNOWN:", EdgeStatus.AR_KNOWN);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var newAr = this.calculateArFromEdges(edgePost);
|
|
||||||
|
|
||||||
this.logger.log('info', 'arDetect_verbose', `%c[ArDetect::frameCheck] Triggering aspect ration change! new ar: ${newAr}`, "color: #aaf");
|
|
||||||
|
|
||||||
// we also know edges for guardline, so set them.
|
|
||||||
// we need to be mindful of fallbackMode though
|
|
||||||
// if edges are okay and not invalid, we also
|
|
||||||
// allow automatic aspect ratio correction. If edges
|
|
||||||
// are bogus, we remain aspect ratio unchanged.
|
|
||||||
try {
|
|
||||||
if (!this.fallbackMode) {
|
|
||||||
// throws error if top/bottom are invalid
|
|
||||||
this.guardLine.setBlackbar({top: edgePost.guardLineTop, bottom: edgePost.guardLineBottom});
|
|
||||||
} else {
|
|
||||||
if (this.conf.player.dimensions){
|
|
||||||
this.guardLine.setBlackbarManual({
|
|
||||||
top: this.settings.active.arDetect.fallbackMode.noTriggerZonePx,
|
|
||||||
bottom: this.conf.player.dimensions.height - this.settings.active.arDetect.fallbackMode.noTriggerZonePx - 1
|
|
||||||
},{
|
|
||||||
top: edgePost.guardLineTop + this.settings.active.arDetect.guardLine.edgeTolerancePx,
|
|
||||||
bottom: edgePost.guardLineBottom - this.settings.active.arDetect.guardLine.edgeTolerancePx
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.processAr(newAr);
|
|
||||||
} catch (e) {
|
|
||||||
// edges weren't gucci, so we'll just reset
|
|
||||||
// the aspect ratio to defaults
|
|
||||||
try {
|
|
||||||
this.guardline.reset();
|
|
||||||
} catch (e) {
|
|
||||||
// guardline wasn't gucci either, but we'll just ignore
|
|
||||||
// that and reset the aspect ratio anyway
|
|
||||||
}
|
|
||||||
this.conf.resizer.setAr({type: AspectRatio.Automatic, ratio: this.getDefaultAr()});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resetBlackLevel(){
|
|
||||||
this.blackLevel = this.settings.active.arDetect.blackbar.blackLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
blackLevelTest_full() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
blackframeTest() {
|
|
||||||
if (this.blackLevel === undefined) {
|
|
||||||
this.logger.log('info', 'arDetect_verbose', "[ArDetect::blackframeTest] black level undefined, resetting");
|
|
||||||
this.resetBlackLevel();
|
|
||||||
}
|
|
||||||
|
|
||||||
const rows = this.blackframeCanvas.height;
|
|
||||||
const cols = this.blackframeCanvas.width;
|
|
||||||
const pixels = rows * cols;
|
|
||||||
let cumulative_r = 0, cumulative_g = 0, cumulative_b = 0;
|
|
||||||
let max_r = 0, max_g = 0, max_b = 0;
|
|
||||||
let avg_r, avg_g, avg_b;
|
|
||||||
let var_r = 0, var_g = 0, var_b = 0;
|
|
||||||
|
|
||||||
let pixelMax = 0;
|
|
||||||
let cumulativeValue = 0;
|
|
||||||
let blackPixelCount = 0;
|
|
||||||
const bfImageData = this.blackframeContext.getImageData(0, 0, cols, rows).data;
|
|
||||||
const blackTreshold = this.blackLevel + this.settings.active.arDetect.blackbar.frameTreshold;
|
|
||||||
|
|
||||||
|
|
||||||
// we do some recon for letterbox and pillarbox. While this can't determine whether letterbox/pillarbox exists
|
|
||||||
// with sufficient level of certainty due to small sample resolution, it can still give us some hints for later
|
|
||||||
let rowMax = new Array(rows).fill(0);
|
|
||||||
let colMax = new Array(cols).fill(0);
|
|
||||||
|
|
||||||
let r, c;
|
|
||||||
|
|
||||||
|
|
||||||
for (let i = 0; i < bfImageData.length; i+= 4) {
|
|
||||||
pixelMax = Math.max(bfImageData[i], bfImageData[i+1], bfImageData[i+2]);
|
|
||||||
bfImageData[i+3] = pixelMax;
|
|
||||||
|
|
||||||
if (pixelMax < blackTreshold) {
|
|
||||||
if (pixelMax < this.blackLevel) {
|
|
||||||
this.blackLevel = pixelMax;
|
|
||||||
}
|
|
||||||
blackPixelCount++;
|
|
||||||
} else {
|
|
||||||
cumulativeValue += pixelMax;
|
|
||||||
cumulative_r += bfImageData[i];
|
|
||||||
cumulative_g += bfImageData[i+1];
|
|
||||||
cumulative_b += bfImageData[i+2];
|
|
||||||
|
|
||||||
max_r = max_r > bfImageData[i] ? max_r : bfImageData[i];
|
|
||||||
max_g = max_g > bfImageData[i+1] ? max_g : bfImageData[i+1];
|
|
||||||
max_b = max_b > bfImageData[i+2] ? max_b : bfImageData[i+2];
|
|
||||||
}
|
|
||||||
|
|
||||||
r = ~~(i/rows);
|
|
||||||
c = i % cols;
|
|
||||||
|
|
||||||
if (pixelMax > rowMax[r]) {
|
|
||||||
rowMax[r] = pixelMax;
|
|
||||||
}
|
|
||||||
if (pixelMax > colMax[c]) {
|
|
||||||
colMax[c] = colMax;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
max_r = 1 / (max_r || 1);
|
|
||||||
max_g = 1 / (max_g || 1);
|
|
||||||
max_b = 1 / (max_b || 1);
|
|
||||||
|
|
||||||
const imagePixels = pixels - blackPixelCount;
|
|
||||||
// calculate averages and normalize them
|
|
||||||
avg_r = (cumulative_r / imagePixels) * max_r;
|
|
||||||
avg_g = (cumulative_g / imagePixels) * max_g;
|
|
||||||
avg_b = (cumulative_b / imagePixels) * max_b;
|
|
||||||
|
|
||||||
// second pass for color variance
|
|
||||||
for (let i = 0; i < bfImageData.length; i+= 4) {
|
|
||||||
if (bfImageData[i+3] >= this.blackLevel) {
|
|
||||||
var_r += Math.abs(avg_r - bfImageData[i] * max_r);
|
|
||||||
var_g += Math.abs(avg_g - bfImageData[i+1] * max_g);
|
|
||||||
var_b += Math.abs(avg_b - bfImageData[i+1] * max_b);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const hasSufficientVariance = Math.abs(var_r - var_g) / Math.max(var_r, var_g, 1) > this.settings.active.arDetect.blackframe.sufficientColorVariance
|
|
||||||
|| Math.abs(var_r - var_b) / Math.max(var_r, var_b, 1) > this.settings.active.arDetect.blackframe.sufficientColorVariance
|
|
||||||
|| Math.abs(var_b - var_g) / Math.max(var_b, var_g, 1) > this.settings.active.arDetect.blackframe.sufficientColorVariance
|
|
||||||
|
|
||||||
let isBlack = (blackPixelCount/(cols * rows) > this.settings.active.arDetect.blackframe.blackPixelsCondition);
|
|
||||||
|
|
||||||
if (! isBlack) {
|
|
||||||
if (hasSufficientVariance) {
|
|
||||||
isBlack = cumulativeValue < this.settings.active.arDetect.blackframe.cumulativeThresholdLax;
|
|
||||||
} else {
|
|
||||||
isBlack = cumulativeValue < this.settings.active.arDetect.blackframe.cumulativeThresholdStrict;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Debug.debug) {
|
|
||||||
return {
|
|
||||||
isBlack: isBlack,
|
|
||||||
blackPixelCount: blackPixelCount,
|
|
||||||
blackPixelRatio: (blackPixelCount/(cols * rows)),
|
|
||||||
cumulativeValue: cumulativeValue,
|
|
||||||
hasSufficientVariance: hasSufficientVariance,
|
|
||||||
blackLevel: this.blackLevel,
|
|
||||||
variances: {
|
|
||||||
raw: {
|
|
||||||
r: var_r, g: var_g, b: var_b
|
|
||||||
},
|
|
||||||
relative: {
|
|
||||||
rg: Math.abs(var_r - var_g) / Math.max(var_r, var_g, 1),
|
|
||||||
rb: Math.abs(var_r - var_b) / Math.max(var_r, var_b, 1),
|
|
||||||
gb: Math.abs(var_b - var_g) / Math.max(var_b, var_g, 1),
|
|
||||||
},
|
|
||||||
relativePercent: {
|
|
||||||
rg: Math.abs(var_r - var_g) / Math.max(var_r, var_g, 1) / this.settings.active.arDetect.blackframe.sufficientColorVariance,
|
|
||||||
rb: Math.abs(var_r - var_b) / Math.max(var_r, var_b, 1) / this.settings.active.arDetect.blackframe.sufficientColorVariance,
|
|
||||||
gb: Math.abs(var_b - var_g) / Math.max(var_b, var_g, 1) / this.settings.active.arDetect.blackframe.sufficientColorVariance,
|
|
||||||
},
|
|
||||||
varianceLimit: this.settings.active.arDetect.blackframe.sufficientColorVariance,
|
|
||||||
},
|
|
||||||
cumulativeValuePercent: cumulativeValue / (hasSufficientVariance ? this.settings.active.arDetect.blackframe.cumulativeThresholdLax : this.settings.active.arDetect.blackframe.cumulativeThresholdStrict),
|
|
||||||
rowMax: rowMax,
|
|
||||||
colMax: colMax,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
isBlack: isBlack,
|
|
||||||
rowMax: rowMax,
|
|
||||||
colMax: colMax,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fastLetterboxPresenceTest(imageData, sampleCols) {
|
|
||||||
// fast test to see if aspect ratio is correct.
|
|
||||||
// returns 'true' if presence of letterbox is possible.
|
|
||||||
// returns 'false' if we found a non-black edge pixel.
|
|
||||||
|
|
||||||
// If we detect anything darker than blackLevel, we modify blackLevel to the new lowest value
|
|
||||||
const rowOffset = this.canvas.width * (this.canvas.height - 1);
|
|
||||||
let currentMin = 255, currentMax = 0, colOffset_r, colOffset_g, colOffset_b, colOffset_rb, colOffset_gb, colOffset_bb, blthreshold = this.settings.active.arDetect.blackbar.threshold;
|
|
||||||
|
|
||||||
// detect black level. if currentMax comes above blackbar + blackbar threshold, we know we aren't letterboxed
|
|
||||||
|
|
||||||
for (var i = 0; i < sampleCols.length; ++i){
|
|
||||||
colOffset_r = sampleCols[i] << 2;
|
|
||||||
colOffset_g = colOffset_r + 1;
|
|
||||||
colOffset_b = colOffset_r + 2;
|
|
||||||
colOffset_rb = colOffset_r + rowOffset;
|
|
||||||
colOffset_gb = colOffset_g + rowOffset;
|
|
||||||
colOffset_bb = colOffset_b + rowOffset;
|
|
||||||
|
|
||||||
currentMax = Math.max(
|
|
||||||
imageData[colOffset_r], imageData[colOffset_g], imageData[colOffset_b],
|
|
||||||
// imageData[colOffset_rb], imageData[colOffset_gb], imageData[colOffset_bb],
|
|
||||||
currentMax
|
|
||||||
);
|
|
||||||
|
|
||||||
if (currentMax > this.blackLevel + blthreshold) {
|
|
||||||
// we search no further
|
|
||||||
if (currentMin < this.blackLevel) {
|
|
||||||
this.blackLevel = currentMin;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentMin = Math.min(
|
|
||||||
currentMax,
|
|
||||||
currentMin
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentMin < this.blackLevel)
|
|
||||||
this.blackLevel = currentMin
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ard_console_stop = "background: #000; color: #f41";
|
|
||||||
var _ard_console_start = "background: #000; color: #00c399";
|
|
||||||
var _ard_console_change = "background: #000; color: #ff8";
|
|
||||||
|
|
||||||
export default ArDetector;
|
|
||||||
@ -1,129 +0,0 @@
|
|||||||
class DebugCanvas {
|
|
||||||
constructor(ardConf){
|
|
||||||
this.conf = ardConf;
|
|
||||||
this.targetWidth = 1280;
|
|
||||||
this.targetHeight = 720;
|
|
||||||
this.targetOffsetTop = 1080;
|
|
||||||
this.targetOffsetLeft = 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
init(canvasSize, canvasPosition, targetCanvasSize) {
|
|
||||||
console.log("initiating DebugCanvas")
|
|
||||||
|
|
||||||
var body = document.getElementsByTagName('body')[0];
|
|
||||||
|
|
||||||
if(!canvasPosition){
|
|
||||||
canvasPosition = {
|
|
||||||
top: 1200,
|
|
||||||
left: 800
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(!this.canvas){
|
|
||||||
this.canvas = document.createElement("canvas");
|
|
||||||
body.appendChild(this.canvas);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(targetCanvasSize){
|
|
||||||
this.targetWidth = targetCanvasSize.width;
|
|
||||||
this.targetHeight = targetCanvasSize.height;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.canvas.style.position = "absolute";
|
|
||||||
this.canvas.style.left = `${canvasPosition.left}px`;
|
|
||||||
this.canvas.style.top = `${canvasPosition.top}px`;
|
|
||||||
this.canvas.style.zIndex = 10002;
|
|
||||||
// this.canvas.id = "uw_debug_canvas";
|
|
||||||
|
|
||||||
this.context = this.canvas.getContext("2d");
|
|
||||||
|
|
||||||
this.canvas.width = canvasSize.width;
|
|
||||||
this.canvas.height = canvasSize.height;
|
|
||||||
|
|
||||||
this.calculateCanvasZoom();
|
|
||||||
|
|
||||||
console.log("debug canvas is:", this.canvas, "context:", this.context)
|
|
||||||
}
|
|
||||||
|
|
||||||
calculateCanvasZoom(){
|
|
||||||
var canvasZoom = this.targetWidth / this.canvas.width;
|
|
||||||
var translateX = (this.canvas.width - this.targetWidth)/2;
|
|
||||||
var translateY = (this.canvas.height - this.targetHeight)/2;
|
|
||||||
|
|
||||||
this.canvas.style.transform = `scale(${canvasZoom},${canvasZoom}) translateX(${translateX}px) translateY(${translateY}px)`;
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy(){
|
|
||||||
if(this.canvas)
|
|
||||||
this.canvas.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
setBuffer(buffer) {
|
|
||||||
// this.imageBuffer = buffer.splice(0);
|
|
||||||
this.imageBuffer = new Uint8ClampedArray(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
trace(arrayIndex, colorClass) {
|
|
||||||
this.imageBuffer[arrayIndex ] = colorClass.colorRgb[0];
|
|
||||||
this.imageBuffer[arrayIndex+1] = colorClass.colorRgb[1];
|
|
||||||
this.imageBuffer[arrayIndex+2] = colorClass.colorRgb[2];
|
|
||||||
}
|
|
||||||
|
|
||||||
update() {
|
|
||||||
var start = performance.now();
|
|
||||||
try{
|
|
||||||
if(this.context && this.imageBuffer instanceof Uint8ClampedArray){
|
|
||||||
try{
|
|
||||||
var idata = new ImageData(this.imageBuffer, this.canvas.width, this.canvas.height);
|
|
||||||
} catch (ee) {
|
|
||||||
console.log("[DebugCanvas.js::update] can't create image data. Trying to correct canvas size. Error was:", ee);
|
|
||||||
this.canvas.width = this.conf.canvas.width;
|
|
||||||
this.canvas.height = this.conf.canvas.height;
|
|
||||||
|
|
||||||
this.calculateCanvasZoom();
|
|
||||||
// this.context = this.canvas.getContext("2d");
|
|
||||||
var idata = new ImageData(this.imageBuffer, this.canvas.width, this.canvas.height);
|
|
||||||
}
|
|
||||||
this.putImageData(this.context, idata, 0, 0, 0, 0, this.canvas.width, this.canvas.height);
|
|
||||||
}
|
|
||||||
} catch(e) {
|
|
||||||
console.log("[DebugCanvas.js::update] updating canvas failed.", e);
|
|
||||||
}
|
|
||||||
console.log("[DebugCanvas.js::update] update took", (performance.now() - start), "ms.");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
putImageData(ctx, imageData, dx, dy, dirtyX, dirtyY, dirtyWidth, dirtyHeight) {
|
|
||||||
var data = imageData.data;
|
|
||||||
var height = imageData.height;
|
|
||||||
var width = imageData.width;
|
|
||||||
dirtyX = dirtyX || 0;
|
|
||||||
dirtyY = dirtyY || 0;
|
|
||||||
dirtyWidth = dirtyWidth !== undefined? dirtyWidth: width;
|
|
||||||
dirtyHeight = dirtyHeight !== undefined? dirtyHeight: height;
|
|
||||||
var limitBottom = Math.min(dirtyHeight, height);
|
|
||||||
var limitRight = Math.min(dirtyWidth, width);
|
|
||||||
for (var y = dirtyY; y < limitBottom; y++) {
|
|
||||||
for (var x = dirtyX; x < limitRight; x++) {
|
|
||||||
var pos = y * width + x;
|
|
||||||
ctx.fillStyle = 'rgba(' + data[pos*4+0]
|
|
||||||
+ ',' + data[pos*4+1]
|
|
||||||
+ ',' + data[pos*4+2]
|
|
||||||
+ ',' + (data[pos*4+3]/255) + ')';
|
|
||||||
ctx.fillRect(x + dx, y + dy, 1, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DebugCanvasClasses = {
|
|
||||||
VIOLATION: {color: '#ff0000', colorRgb: [255, 0, 0], text: 'violation (general)'},
|
|
||||||
WARN: {color: '#d0d039', colorRgb: [208, 208, 57], text: 'lesser violation (general)'},
|
|
||||||
GUARDLINE_BLACKBAR: {color: '#3333FF', colorRgb: [51, 51, 255], text: 'guardline/blackbar (expected value)'},
|
|
||||||
GUARDLINE_IMAGE: {color: '#000088', colorRgb: [0, 0, 136], text: 'guardline/image (expected value)'},
|
|
||||||
|
|
||||||
EDGEDETECT_ONBLACK: {color: '#444444', colorRgb: [68, 68, 68], text: 'edge detect - perpendicular (to edge)'},
|
|
||||||
EDGEDETECT_CANDIDATE: {color: '#FFFFFF', colorRgb: [255, 255, 255], text: 'edge detect - edge candidate'},
|
|
||||||
EDGEDETECT_CANDIDATE_SECONDARY: {color: '#OOOOOO', colorRgb: [0, 0, 0], text: 'edge detect - edge candidate, but for when candidate is really bright'},
|
|
||||||
EDGEDETECT_BLACKBAR: {color: '#07ac93', colorRgb: [7, 172, 147], text: 'edge detect - parallel, black test'},
|
|
||||||
EDGEDETECT_IMAGE: {color: '#046c5c', colorRgb: [4, 108, 92], text: 'edge detect - parallel, image test'}
|
|
||||||
}
|
|
||||||
@ -1,305 +0,0 @@
|
|||||||
import Debug from '../../conf/Debug';
|
|
||||||
|
|
||||||
class GuardLine {
|
|
||||||
|
|
||||||
// ardConf — reference to ArDetector that has current GuardLine instance
|
|
||||||
constructor(ardConf){
|
|
||||||
this.blackbar = {top: undefined, bottom: undefined};
|
|
||||||
this.imageBar = {top: undefined, bottom: undefined};
|
|
||||||
|
|
||||||
this.conf = ardConf;
|
|
||||||
this.settings = ardConf.settings;
|
|
||||||
}
|
|
||||||
|
|
||||||
reset() {
|
|
||||||
this.blackbar = {top: undefined, bottom: undefined};
|
|
||||||
this.imageBar = {top: undefined, bottom: undefined};
|
|
||||||
}
|
|
||||||
|
|
||||||
setBlackbarManual(blackbarConf, imagebarConf){
|
|
||||||
// ni lepo uporabljat tega, ampak pri fallback mode nastavljamo blackbar stuff na roke
|
|
||||||
// it's not nice to use this, but we're setting these values manually in fallbackMode
|
|
||||||
if (blackbarConf) {
|
|
||||||
this.blackbar = blackbarConf;
|
|
||||||
}
|
|
||||||
if (imagebarConf) {
|
|
||||||
this.imageBar = imagebarConf;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setBlackbar(bbconf){
|
|
||||||
var bbTop = bbconf.top - this.settings.active.arDetect.guardLine.edgeTolerancePx;
|
|
||||||
var bbBottom = bbconf.bottom + this.settings.active.arDetect.guardLine.edgeTolerancePx;
|
|
||||||
|
|
||||||
// to odstrani vse neveljavne nastavitve in vse možnosti, ki niso smiselne
|
|
||||||
// this removes any configs with invalid values or values that dont make sense
|
|
||||||
if (bbTop < 0 || bbBottom >= this.conf.canvas.height ){
|
|
||||||
throw "INVALID_SETTINGS_IN_GUARDLINE"
|
|
||||||
}
|
|
||||||
|
|
||||||
this.blackbar = {
|
|
||||||
top: bbTop,
|
|
||||||
bottom: bbBottom
|
|
||||||
}
|
|
||||||
|
|
||||||
this.imageBar = {
|
|
||||||
top: bbconf.top + 1 + this.settings.active.arDetect.guardLine.edgeTolerancePx,
|
|
||||||
bottom: bbconf.bottom - 1 - this.settings.active.arDetect.guardLine.edgeTolerancePx
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
check(image, fallbackMode){
|
|
||||||
// izračunaj enkrat in shrani na objekt
|
|
||||||
// calculate once and save object-instance-wide
|
|
||||||
this.blackbarThreshold = this.conf.blackLevel + this.settings.active.arDetect.blackbar.threshold;
|
|
||||||
this.imageThreshold = this.blackbarThreshold + this.settings.active.arDetect.blackbar.imageThreshold;
|
|
||||||
|
|
||||||
// dejansko testiranje
|
|
||||||
// actual checks
|
|
||||||
var guardLineResult = this.guardLineCheck(image, fallbackMode);
|
|
||||||
|
|
||||||
// Zaznali smo kršitev črnega dela, zato nam ni treba preveriti, ali je slika
|
|
||||||
// prisotna. Vemo namreč, da se je razmerje stranic zmanjšalo.
|
|
||||||
//
|
|
||||||
// blackbar violation detected. We don't even need to check for presence of image
|
|
||||||
// as aspect ratio just decreased
|
|
||||||
if(! guardLineResult.success) {
|
|
||||||
return {
|
|
||||||
blackbarFail: true,
|
|
||||||
offenders: guardLineResult.offenders,
|
|
||||||
imageFail: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var imageCheckResult = this.imageCheck(image, fallbackMode);
|
|
||||||
|
|
||||||
return {
|
|
||||||
blackbarFail: false,
|
|
||||||
imageFail: ! imageCheckResult.success
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// don't use methods below this line outside this class
|
|
||||||
guardLineCheck(image, fallbackMode){
|
|
||||||
// this test tests for whether we crop too aggressively
|
|
||||||
|
|
||||||
// if this test is passed, then aspect ratio probably didn't change from wider to narrower. However, further
|
|
||||||
// checks are needed to determine whether aspect ratio got wider.
|
|
||||||
// if this test fails, it returns a list of offending points.
|
|
||||||
|
|
||||||
// if the upper edge is null, then edge hasn't been detected before. This test is pointless, therefore it
|
|
||||||
// should succeed by default. Also need to check bottom, for cases where only one edge is known
|
|
||||||
|
|
||||||
if(! fallbackMode && (! this.blackbar.top || ! this.blackbar.bottom)) {
|
|
||||||
return { success: true };
|
|
||||||
}
|
|
||||||
|
|
||||||
var offset = parseInt(this.conf.canvas.width * this.settings.active.arDetect.guardLine.ignoreEdgeMargin) << 2;
|
|
||||||
|
|
||||||
var offenders = [];
|
|
||||||
var offenderCount = -1; // doing it this way means first offender has offenderCount==0. Ez index.
|
|
||||||
|
|
||||||
// TODO: implement logo check.
|
|
||||||
|
|
||||||
// preglejmo obe vrstici
|
|
||||||
// check both rows
|
|
||||||
|
|
||||||
if(! fallbackMode){
|
|
||||||
var edge_upper = this.blackbar.top;
|
|
||||||
var edge_lower = this.blackbar.bottom;
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
// fallback mode is a bit different
|
|
||||||
edge_upper = 0;
|
|
||||||
edge_lower = this.conf.canvas.height - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
var rowStart, rowEnd;
|
|
||||||
|
|
||||||
// <<<=======| checking upper row |========>>>
|
|
||||||
|
|
||||||
rowStart = ((edge_upper * this.conf.canvas.width) << 2) + offset;
|
|
||||||
rowEnd = rowStart + ( this.conf.canvas.width << 2 ) - (offset * 2);
|
|
||||||
|
|
||||||
if (Debug.debugCanvas.enabled && Debug.debugCanvas.guardLine) {
|
|
||||||
offenderCount = this._gl_debugRowCheck(image, rowStart, rowEnd, offenders, offenderCount);
|
|
||||||
} else {
|
|
||||||
offenderCount = this._gl_rowCheck(image, rowStart, rowEnd, offenders, offenderCount);
|
|
||||||
}
|
|
||||||
// <<<=======| checking lower row |========>>>
|
|
||||||
|
|
||||||
rowStart = ((edge_lower * this.conf.canvas.width) << 2) + offset;
|
|
||||||
rowEnd = rowStart + ( this.conf.canvas.width << 2 ) - (offset * 2);
|
|
||||||
|
|
||||||
if (Debug.debugCanvas.enabled && Debug.debugCanvas.guardLine) {
|
|
||||||
offenderCount = this._gl_debugRowCheck(image, rowStart, rowEnd, offenders, offenderCount);
|
|
||||||
} else {
|
|
||||||
offenderCount = this._gl_rowCheck(image, rowStart, rowEnd, offenders, offenderCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
// če nismo našli nobenih prekrškarjev, vrnemo uspeh. Drugače vrnemo seznam prekrškarjev
|
|
||||||
// vrnemo tabelo, ki vsebuje sredinsko točko vsakega prekrškarja (x + width*0.5)
|
|
||||||
//
|
|
||||||
// if we haven't found any offenders, we return success. Else we return list of offenders
|
|
||||||
// we return array of middle points of offenders (x + (width * 0.5) for every offender)
|
|
||||||
|
|
||||||
if(offenderCount == -1){
|
|
||||||
return {success: true};
|
|
||||||
}
|
|
||||||
|
|
||||||
var ret = new Array(offenders.length);
|
|
||||||
for(var o in offenders){
|
|
||||||
ret[o] = offenders[o].x + (offenders[o].width * 0.25);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {success: false, offenders: ret};
|
|
||||||
}
|
|
||||||
|
|
||||||
imageCheck(image){
|
|
||||||
if(!this.imageBar.top || !this.imageBar.bottom)
|
|
||||||
return { success: false };
|
|
||||||
|
|
||||||
var offset = (this.conf.canvas.width * this.settings.active.arDetect.guardLine.ignoreEdgeMargin) << 2;
|
|
||||||
|
|
||||||
// TODO: implement logo check.
|
|
||||||
|
|
||||||
|
|
||||||
// preglejmo obe vrstici - tukaj po pravilih ne bi smeli iti prek mej platna. ne rabimo preverjati
|
|
||||||
// check both rows - by the rules and definitions, we shouldn't go out of bounds here. no need to check, then
|
|
||||||
|
|
||||||
// if(fallbackMode){
|
|
||||||
// var edge_upper = this.settings.active.arDetect.fallbackMode.noTriggerZonePx;
|
|
||||||
// var edge_lower = this.conf.canvas.height - this.settings.active.arDetect.fallbackMode.noTriggerZonePx - 1;
|
|
||||||
// }
|
|
||||||
// else{
|
|
||||||
var edge_upper = this.imageBar.top;
|
|
||||||
var edge_lower = this.imageBar.bottom;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// koliko pikslov rabimo zaznati, da je ta funkcija uspe. Tu dovoljujemo tudi, da so vsi piksli na enem
|
|
||||||
// robu (eden izmed robov je lahko v celoti črn)
|
|
||||||
// how many non-black pixels we need to consider this check a success. We only need to detect enough pixels
|
|
||||||
// on one edge (one of the edges can be black as long as both aren't)
|
|
||||||
var successThreshold = (this.conf.canvas.width * this.settings.active.arDetect.guardLine.imageTestThreshold);
|
|
||||||
var rowStart, rowEnd;
|
|
||||||
|
|
||||||
|
|
||||||
// <<<=======| checking upper row |========>>>
|
|
||||||
|
|
||||||
rowStart = ((edge_upper * this.conf.canvas.width) << 2) + offset;
|
|
||||||
rowEnd = rowStart + ( this.conf.canvas.width << 2 ) - (offset * 2);
|
|
||||||
|
|
||||||
var res = false;
|
|
||||||
|
|
||||||
if(Debug.debugCanvas.enabled && Debug.debugCanvas.guardLine){
|
|
||||||
res = this._ti_debugCheckRow(image, rowStart, rowEnd, successThreshold);
|
|
||||||
} else {
|
|
||||||
res = this._ti_checkRow(image, rowStart, rowEnd,successThreshold);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(res)
|
|
||||||
return {success: true};
|
|
||||||
|
|
||||||
|
|
||||||
// <<<=======| checking lower row |========>>>
|
|
||||||
|
|
||||||
rowStart = ((edge_lower * this.conf.canvas.width) << 2) + offset;
|
|
||||||
// rowEnd = rowStart + ( this.conf.canvas.width << 2 ) - (offset * 2);
|
|
||||||
|
|
||||||
|
|
||||||
if(Debug.debugCanvas.enabled && Debug.debugCanvas.guardLine){
|
|
||||||
res = this._ti_debugCheckRow(image, rowStart, rowEnd, successThreshold);
|
|
||||||
} else {
|
|
||||||
res = this._ti_checkRow(image, rowStart, rowEnd,successThreshold);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {success: res};
|
|
||||||
}
|
|
||||||
|
|
||||||
// pomožne metode
|
|
||||||
// helper methods
|
|
||||||
|
|
||||||
|
|
||||||
_gl_rowCheck(image, rowStart, rowEnd, offenders, offenderCount){
|
|
||||||
var firstOffender = -1;
|
|
||||||
for(var i = rowStart; i < rowEnd; i+=4){
|
|
||||||
|
|
||||||
// we track sections that go over what's supposed to be a black line, so we can suggest more
|
|
||||||
// columns to sample
|
|
||||||
if(image[i] > this.blackbarThreshold || image[i+1] > this.blackbarThreshold || image[i+2] > this.blackbarThreshold){
|
|
||||||
if(firstOffender < 0){
|
|
||||||
firstOffender = (i - rowStart) >> 2;
|
|
||||||
offenderCount++;
|
|
||||||
offenders.push({x: firstOffender, width: 1});
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
offenders[offenderCount].width++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
// is that a black pixel again? Let's reset the 'first offender'
|
|
||||||
firstOffender = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return offenderCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
_gl_debugRowCheck(image, rowStart, rowEnd, offenders, offenderCount){
|
|
||||||
var firstOffender = -1;
|
|
||||||
for(var i = rowStart; i < rowEnd; i+=4){
|
|
||||||
|
|
||||||
// we track sections that go over what's supposed to be a black line, so we can suggest more
|
|
||||||
// columns to sample
|
|
||||||
if(image[i] > this.blackbarThreshold || image[i+1] > this.blackbarThreshold || image[i+2] > this.blackbarThreshold){
|
|
||||||
this.conf.debugCanvas.trace(i, DebugCanvasClasses.VIOLATION);
|
|
||||||
if(firstOffender < 0){
|
|
||||||
firstOffender = (i - rowStart) >> 2;
|
|
||||||
offenderCount++;
|
|
||||||
offenders.push({x: firstOffender, width: 1});
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
offenders[offenderCount].width++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
this.conf.debugCanvas.trace(i, DebugCanvasClasses.GUARDLINE_BLACKBAR);
|
|
||||||
// is that a black pixel again? Let's reset the 'first offender'
|
|
||||||
firstOffender = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return offenderCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
_ti_checkRow(image, rowStart, rowEnd, successThreshold) {
|
|
||||||
for(var i = rowStart; i < rowEnd; i+=4){
|
|
||||||
if(image[i] > this.imageThreshold || image[i+1] > this.imageThreshold || image[i+2] > this.imageThreshold){
|
|
||||||
if(successThreshold --<= 0){
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_ti_debugCheckRow(image, rowStart, rowEnd, successThreshold) {
|
|
||||||
for(var i = rowStart; i < rowEnd; i+=4){
|
|
||||||
if(image[i] > this.imageThreshold || image[i+1] > this.imageThreshold || image[i+2] > this.imageThreshold){
|
|
||||||
this.conf.debugCanvas.trace(i, DebugCanvasClasses.GUARDLINE_IMAGE);
|
|
||||||
if(successThreshold --<= 0){
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.conf.debugCanvas.trace(i, DebugCanvasClasses.WARN);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default GuardLine;
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,6 +0,0 @@
|
|||||||
var EdgeDetectPrimaryDirection = Object.freeze({
|
|
||||||
VERTICAL: 0,
|
|
||||||
HORIZONTAL: 1
|
|
||||||
});
|
|
||||||
|
|
||||||
export default EdgeDetectPrimaryDirection;
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
var EdgeDetectQuality = Object.freeze({
|
|
||||||
FAST: 0,
|
|
||||||
IMPROVED: 1
|
|
||||||
});
|
|
||||||
|
|
||||||
export default EdgeDetectQuality;
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
var EdgeStatus = Object.freeze({
|
|
||||||
AR_UNKNOWN: 0,
|
|
||||||
AR_KNOWN: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default EdgeStatus;
|
|
||||||
@ -1,35 +0,0 @@
|
|||||||
import Debug from '../../conf/Debug';
|
|
||||||
import BrowserDetect from '../../conf/BrowserDetect';
|
|
||||||
|
|
||||||
class Comms {
|
|
||||||
static async sendMessage(message){
|
|
||||||
|
|
||||||
if(BrowserDetect.firefox){
|
|
||||||
return browser.runtime.sendMessage(message)
|
|
||||||
} else {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
try{
|
|
||||||
if(BrowserDetect.edge){
|
|
||||||
browser.runtime.sendMessage(message, function(response){
|
|
||||||
var r = response;
|
|
||||||
resolve(r);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
chrome.runtime.sendMessage(message, function(response){
|
|
||||||
// Chrome/js shittiness mitigation — remove this line and an empty array will be returned
|
|
||||||
var r = response;
|
|
||||||
resolve(r);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch(e){
|
|
||||||
reject(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Comms;
|
|
||||||
@ -1,178 +0,0 @@
|
|||||||
import Debug from '../../conf/Debug';
|
|
||||||
import BrowserDetect from '../../conf/BrowserDetect';
|
|
||||||
|
|
||||||
class CommsClient {
|
|
||||||
constructor(name, settings, logger) {
|
|
||||||
this.logger = logger;
|
|
||||||
if (BrowserDetect.firefox) {
|
|
||||||
this.port = browser.runtime.connect({name: name});
|
|
||||||
} else if (BrowserDetect.chrome) {
|
|
||||||
this.port = chrome.runtime.connect({name: name});
|
|
||||||
} else if (BrowserDetect.edge) {
|
|
||||||
this.port = browser.runtime.connect({name: name})
|
|
||||||
}
|
|
||||||
|
|
||||||
var ths = this;
|
|
||||||
this._listener = m => ths.processReceivedMessage(m);
|
|
||||||
this.port.onMessage.addListener(this._listener);
|
|
||||||
|
|
||||||
this.settings = settings;
|
|
||||||
this.pageInfo = undefined;
|
|
||||||
this.commsId = (Math.random() * 20).toFixed(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy() {
|
|
||||||
this.pageInfo = null;
|
|
||||||
this.settings = null;
|
|
||||||
if (!BrowserDetect.edge) { // edge is a very special browser made by outright morons.
|
|
||||||
this.port.onMessage.removeListener(this._listener);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setPageInfo(pageInfo){
|
|
||||||
|
|
||||||
this.pageInfo = pageInfo;
|
|
||||||
|
|
||||||
this.logger.log('info', 'debug', `[CommsClient::setPageInfo] <${this.commsId}>`, "SETTING PAGEINFO —", this.pageInfo, this)
|
|
||||||
|
|
||||||
var ths = this;
|
|
||||||
this._listener = m => ths.processReceivedMessage(m);
|
|
||||||
if (!BrowserDetect.edge) {
|
|
||||||
this.port.onMessage.removeListener(this._listener);
|
|
||||||
}
|
|
||||||
this.port.onMessage.addListener(this._listener);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
processReceivedMessage(message){
|
|
||||||
this.logger.log('info', 'comms', `[CommsClient.js::processMessage] <${this.commsId}> Received message from background script!`, message);
|
|
||||||
|
|
||||||
if (!this.pageInfo || !this.settings.active) {
|
|
||||||
this.logger.log('info', 'comms', `[CommsClient.js::processMessage] <${this.commsId}> this.pageInfo (or settings) not defined. Extension is probably disabled for this site.\npageInfo:`, this.pageInfo,
|
|
||||||
"\nsettings.active:", this.settings.active,
|
|
||||||
"\nnobj:", this
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message.cmd === 'get-current-zoom') {
|
|
||||||
this.pageInfo.requestCurrentZoom();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message.cmd === "set-ar") {
|
|
||||||
this.pageInfo.setAr({type: message.arg, ratio: message.customArg}, message.playing);
|
|
||||||
} else if (message.cmd === 'set-alignment') {
|
|
||||||
this.pageInfo.setVideoAlignment(message.arg, message.playing);
|
|
||||||
this.pageInfo.restoreAr();
|
|
||||||
} else if (message.cmd === "set-stretch") {
|
|
||||||
this.pageInfo.setStretchMode(message.arg, message.playing);
|
|
||||||
} else if (message.cmd === 'set-keyboard') {
|
|
||||||
this.pageInfo.setKeyboardShortcutsEnabled(message.arg)
|
|
||||||
} else if (message.cmd === "autoar-start") {
|
|
||||||
if (message.enabled !== false) {
|
|
||||||
this.pageInfo.initArDetection(message.playing);
|
|
||||||
this.pageInfo.startArDetection(message.playing);
|
|
||||||
} else {
|
|
||||||
this.pageInfo.stopArDetection(message.playing);
|
|
||||||
}
|
|
||||||
} else if (message.cmd === "pause-processing") {
|
|
||||||
this.pageInfo.pauseProcessing(message.playing);
|
|
||||||
} else if (message.cmd === "resume-processing") {
|
|
||||||
// todo: autoArStatus
|
|
||||||
this.pageInfo.resumeProcessing(message.autoArStatus, message.playing);
|
|
||||||
} else if (message.cmd === 'set-zoom') {
|
|
||||||
this.pageInfo.setZoom(message.arg, true, message.playing);
|
|
||||||
} else if (message.cmd === 'change-zoom') {
|
|
||||||
this.pageInfo.zoomStep(message.arg, message.playing);
|
|
||||||
} else if (message.cmd === 'mark-player') {
|
|
||||||
this.pageInfo.markPlayer(message.name, message.color);
|
|
||||||
} else if (message.cmd === 'unmark-player') {
|
|
||||||
this.pageInfo.unmarkPlayer();
|
|
||||||
} else if (message.cmd === 'autoar-set-manual-tick') {
|
|
||||||
this.pageInfo.setManualTick(message.arg);
|
|
||||||
} else if (message.cmd === 'autoar-tick') {
|
|
||||||
this.pageInfo.tick();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async sleep(n){
|
|
||||||
return new Promise( (resolve, reject) => setTimeout(resolve, n) );
|
|
||||||
}
|
|
||||||
|
|
||||||
async sendMessage_nonpersistent(message){
|
|
||||||
message = JSON.parse(JSON.stringify(message)); // vue quirk. We should really use vue store instead
|
|
||||||
|
|
||||||
if(BrowserDetect.firefox){
|
|
||||||
return browser.runtime.sendMessage(message)
|
|
||||||
} else {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
try{
|
|
||||||
if(BrowserDetect.edge){
|
|
||||||
browser.runtime.sendMessage(message, function(response){
|
|
||||||
var r = response;
|
|
||||||
resolve(r);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
chrome.runtime.sendMessage(message, function(response){
|
|
||||||
// Chrome/js shittiness mitigation — remove this line and an empty array will be returned
|
|
||||||
var r = response;
|
|
||||||
resolve(r);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch(e){
|
|
||||||
reject(e);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async requestSettings(){
|
|
||||||
this.logger.log('info', 'comms', "%c[CommsClient::requestSettings] sending request for congif!", "background: #11D; color: #aad");
|
|
||||||
|
|
||||||
var response = await this.sendMessage_nonpersistent({cmd: 'get-config'});
|
|
||||||
|
|
||||||
this.logger.log('info', 'comms', "%c[CommsClient::requestSettings] received settings response!", "background: #11D; color: #aad", response);
|
|
||||||
|
|
||||||
if(! response || response.extensionConf){
|
|
||||||
return Promise.resolve(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.settings.active = JSON.parse(response.extensionConf);
|
|
||||||
return Promise.resolve(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
async sendMessage(message) {
|
|
||||||
await this.sendMessage_nonpersistent(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
registerVideo(){
|
|
||||||
this.logger.log('info', 'comms', `[CommsClient::registerVideo] <${this.commsId}>`, "Registering video for current page.");
|
|
||||||
if (this.pageInfo) {
|
|
||||||
if (this.pageInfo.hasVideo()) {
|
|
||||||
this.port.postMessage({cmd: "has-video"});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// this.port.postMessage({cmd: "has-video"});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sendPerformanceUpdate(message){
|
|
||||||
this.port.postMessage({cmd: 'performance-update', message: message});
|
|
||||||
}
|
|
||||||
|
|
||||||
unregisterVideo(){
|
|
||||||
this.logger.log('info', 'comms', `[CommsClient::unregisterVideo] <${this.commsId}>`, "Unregistering video for current page.");
|
|
||||||
this.port.postMessage({cmd: "noVideo"}); // ayymd
|
|
||||||
}
|
|
||||||
|
|
||||||
announceZoom(scale){
|
|
||||||
this.port.postMessage({cmd: "announce-zoom", zoom: scale});
|
|
||||||
this.registerVideo()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default CommsClient;
|
|
||||||
@ -1,236 +0,0 @@
|
|||||||
import Debug from '../../conf/Debug';
|
|
||||||
import BrowserDetect from '../../conf/BrowserDetect';
|
|
||||||
|
|
||||||
class CommsServer {
|
|
||||||
constructor(server) {
|
|
||||||
this.server = server;
|
|
||||||
this.logger = server.logger;
|
|
||||||
this.settings = server.settings;
|
|
||||||
this.ports = [];
|
|
||||||
|
|
||||||
var ths = this;
|
|
||||||
|
|
||||||
if (BrowserDetect.firefox) {
|
|
||||||
browser.runtime.onConnect.addListener(p => ths.onConnect(p));
|
|
||||||
browser.runtime.onMessage.addListener((m, sender) => ths.processReceivedMessage_nonpersistent(m, sender));
|
|
||||||
} else {
|
|
||||||
chrome.runtime.onConnect.addListener(p => ths.onConnect(p));
|
|
||||||
chrome.runtime.onMessage.addListener((m, sender, callback) => ths.processReceivedMessage_nonpersistent(m, sender, callback));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getCurrentTabHostname() {
|
|
||||||
const activeTab = await this._getActiveTab();
|
|
||||||
const url = activeTab[0].url;
|
|
||||||
|
|
||||||
var hostname;
|
|
||||||
|
|
||||||
if (url.indexOf("://") > -1) { //find & remove protocol (http, ftp, etc.) and get hostname
|
|
||||||
hostname = url.split('/')[2];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
hostname = url.split('/')[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
hostname = hostname.split(':')[0]; //find & remove port number
|
|
||||||
hostname = hostname.split('?')[0]; //find & remove "?"
|
|
||||||
|
|
||||||
return hostname;
|
|
||||||
}
|
|
||||||
|
|
||||||
sendToAll(message){
|
|
||||||
for(var p of this.ports){
|
|
||||||
for(var frame in p){
|
|
||||||
p[frame].postMessage(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async _getActiveTab() {
|
|
||||||
if (BrowserDetect.firefox) {
|
|
||||||
return await browser.tabs.query({currentWindow: true, active: true});
|
|
||||||
} else {
|
|
||||||
return await new Promise( (resolve, reject) => {
|
|
||||||
chrome.tabs.query({lastFocusedWindow: true, active: true}, function (res) {
|
|
||||||
resolve(res);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async sendToFrame(message, tab, frame) {
|
|
||||||
this.logger.log('info', 'CommsServer', `%c[CommsServer::sendToFrame] attempting to send message to tab ${tab}, frame ${frame}`, "background: #dda; color: #11D", message);
|
|
||||||
|
|
||||||
if (isNaN(tab)) {
|
|
||||||
if (tab === '__playing') {
|
|
||||||
message['playing'] = true;
|
|
||||||
this.sendToAll(message);
|
|
||||||
return;
|
|
||||||
} else if (tab === '__all') {
|
|
||||||
this.sendToAll(message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
[tab, frame] = tab.split('-')
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.log('info', 'CommsServer', `%c[CommsServer::sendToFrame] attempting to send message to tab ${tab}, frame ${frame}`, "background: #dda; color: #11D", message);
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.ports[tab][frame].postMessage(message);
|
|
||||||
} catch (e) {
|
|
||||||
this.logger.log('error', 'CommsServer', `%c[CommsServer::sendToFrame] Sending message failed. Reason:`, "background: #dda; color: #11D", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async sendToActive(message) {
|
|
||||||
this.logger.log('info', 'CommsServer', "%c[CommsServer::sendToActive] trying to send a message to active tab. Message:", "background: #dda; color: #11D", message);
|
|
||||||
|
|
||||||
var tabs = await this._getActiveTab();
|
|
||||||
|
|
||||||
this.logger.log('info', 'CommsServer', "[CommsServer::_sendToActive] currently active tab(s)?", tabs);
|
|
||||||
for (var key in this.ports[tabs[0].id]) {
|
|
||||||
this.logger.log('info', 'CommsServer', "key?", key, this.ports[tabs[0].id]);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var key in this.ports[tabs[0].id]) {
|
|
||||||
this.ports[tabs[0].id][key].postMessage(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onConnect(port){
|
|
||||||
var ths = this;
|
|
||||||
|
|
||||||
// poseben primer | special case
|
|
||||||
if (port.name === 'popup-port') {
|
|
||||||
this.popupPort = port;
|
|
||||||
this.popupPort.onMessage.addListener( (m,p) => ths.processReceivedMessage(m,p));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var tabId = port.sender.tab.id;
|
|
||||||
var frameId = port.sender.frameId;
|
|
||||||
if(! this.ports[tabId]){
|
|
||||||
this.ports[tabId] = {};
|
|
||||||
}
|
|
||||||
this.ports[tabId][frameId] = port;
|
|
||||||
this.ports[tabId][frameId].onMessage.addListener( (m,p) => ths.processReceivedMessage(m, p));
|
|
||||||
this.ports[tabId][frameId].onDisconnect.addListener( (p) => {
|
|
||||||
delete ths.ports[p.sender.tab.id][p.sender.frameId];
|
|
||||||
if(Object.keys(ths.ports[p.sender.tab.id]).length === 0){
|
|
||||||
ths.ports[tabId] = undefined;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async processReceivedMessage(message, port){
|
|
||||||
this.logger.log('info', 'CommsServer', "[CommsServer.js::processReceivedMessage] Received message from popup/content script!", message, "port", port, "\nsettings and server:", this.settings,this.server);
|
|
||||||
|
|
||||||
if (message.forwardToContentScript) {
|
|
||||||
this.logger.log('info', 'CommsServer', "[CommsServer.js::processReceivedMessage] Message has 'forward to content script' flag set. Forwarding message as is. Message:", message);
|
|
||||||
|
|
||||||
this.sendToFrame(message, message.targetTab, message.targetFrame);
|
|
||||||
}
|
|
||||||
if (message.forwardToAll) {
|
|
||||||
this.logger.log('info', 'CommsServer', "[CommsServer.js::processReceivedMessage] Message has 'forward to all' flag set. Forwarding message as is. Message:", message);
|
|
||||||
this.sendToAll(message);
|
|
||||||
}
|
|
||||||
if (message.forwardToActive) {
|
|
||||||
this.logger.log('info', 'CommsServer', "[CommsServer.js::processReceivedMessage] Message has 'forward to active' flag set. Forwarding message as is. Message:", message);
|
|
||||||
this.sendToActive(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message.cmd === 'announce-zoom') {
|
|
||||||
// forward off to the popup, no use for this here
|
|
||||||
try {
|
|
||||||
this.popupPort.postMessage({cmd: 'set-current-zoom', zoom: message.zoom});
|
|
||||||
} catch (e) {
|
|
||||||
// can't forward stuff to popup if it isn't open
|
|
||||||
}
|
|
||||||
} else if (message.cmd === 'get-current-zoom') {
|
|
||||||
this.sendToActive(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message.cmd === 'get-current-site') {
|
|
||||||
port.postMessage({
|
|
||||||
cmd: 'set-current-site',
|
|
||||||
site: this.server.getVideoTab(),
|
|
||||||
tabHostname: await this.getCurrentTabHostname()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (message.cmd === 'popup-set-selected-tab') {
|
|
||||||
this.server.setSelectedTab(message.selectedMenu, message.selectedSubitem);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message.cmd === 'get-config') {
|
|
||||||
this.logger.log('info', 'CommsServer', "CommsServer: received get-config. Active settings?", this.settings.active, "\n(settings:", this.settings, ")");
|
|
||||||
port.postMessage(
|
|
||||||
{cmd: "set-config", conf: this.settings.active, site: this.server.currentSite}
|
|
||||||
);
|
|
||||||
} else if (message.cmd === 'has-video') {
|
|
||||||
this.server.registerVideo(port.sender);
|
|
||||||
} else if (message.cmd === 'noVideo') {
|
|
||||||
this.server.unregisterVideo(port.sender);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
processReceivedMessage_nonpersistent(message, sender, sendResponse){
|
|
||||||
this.logger.log('info', 'CommsServer', "%c[CommsServer.js::processMessage_nonpersistent] Received message from background script!", "background-color: #11D; color: #aad", message, sender);
|
|
||||||
|
|
||||||
if (message.cmd === 'inject-css') {
|
|
||||||
this.server.injectCss(message.cssString, sender);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (message.cmd === 'remove-css' || message.cmd === 'eject-css') {
|
|
||||||
this.server.removeCss(message.cssString, sender);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (message.cmd === 'replace-css') {
|
|
||||||
this.server.replaceCss(message.oldCssString, message.newCssString, sender);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message.forwardToContentScript) {
|
|
||||||
this.logger.log('info', 'CommsServer', "[CommsServer.js::processMessage_nonpersistent] Message has 'forward to content script' flag set. Forwarding message as is. Message:", message);
|
|
||||||
this.logger.log('info', 'CommsServer', "[CommsServer.js::processMessage_nonpersistent] (btw we probably shouldn't be seeing this. This should prolly happen in persistent connection?");
|
|
||||||
|
|
||||||
this.sendToFrame(message, message.targetFrame);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message.cmd === 'get-config') {
|
|
||||||
if (BrowserDetect.firefox) {
|
|
||||||
var ret = {extensionConf: JSON.stringify(this.settings.active)};
|
|
||||||
this.logger.log('info', 'CommsServer', "%c[CommsServer.js::processMessage_nonpersistent] Returning this:", "background-color: #11D; color: #aad", ret);
|
|
||||||
Promise.resolve(ret);
|
|
||||||
} else {
|
|
||||||
sendResponse({extensionConf: JSON.stringify(this.settings.active)});
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else if (message.cmd === "autoar-enable") {
|
|
||||||
this.settings.active.sites['@global'].autoar = "blacklist";
|
|
||||||
this.settings.save();
|
|
||||||
this.sendToAll({cmd: "reload-settings", sender: "uwbg"})
|
|
||||||
this.logger.log('info', 'CommsServer', "[uw-bg] autoar set to enabled (blacklist). evidenz:", this.settings.active);
|
|
||||||
} else if (message.cmd === "autoar-disable") {
|
|
||||||
this.settings.active.sites['@global'].autoar = "disabled";
|
|
||||||
if(message.reason){
|
|
||||||
this.settings.active.arDetect.disabledReason = message.reason;
|
|
||||||
} else {
|
|
||||||
this.settings.active.arDetect.disabledReason = 'User disabled';
|
|
||||||
}
|
|
||||||
this.settings.save();
|
|
||||||
this.sendToAll({cmd: 'reload-settings', newConf: this.settings.active});
|
|
||||||
this.logger.log('info', 'CommsServer', "[uw-bg] autoar set to disabled. evidenz:", this.settings.active);
|
|
||||||
} else if (message.cmd === "autoar-set-interval") {
|
|
||||||
this.logger.log('info', 'CommsServer', `[uw-bg] trying to set new interval for autoAr. New interval is, ${message.timeout} ms`);
|
|
||||||
|
|
||||||
// set fairly liberal limit
|
|
||||||
var timeout = message.timeout < 4 ? 4 : message.timeout;
|
|
||||||
this.settings.active.arDetect.timer_playing = timeout;
|
|
||||||
this.settings.save();
|
|
||||||
this.sendToAll({cmd: 'reload-settings', newConf: this.settings.active});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default CommsServer;
|
|
||||||
@ -1,556 +0,0 @@
|
|||||||
import Debug from '../../conf/Debug';
|
|
||||||
import VideoData from './VideoData';
|
|
||||||
import RescanReason from './enums/RescanReason';
|
|
||||||
import AspectRatio from '../../../common/enums/aspect-ratio.enum';
|
|
||||||
|
|
||||||
if(Debug.debug)
|
|
||||||
console.log("Loading: PageInfo.js");
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class PageInfo {
|
|
||||||
constructor(comms, settings, logger, extensionMode, readOnly = false){
|
|
||||||
this.logger = logger;
|
|
||||||
this.hasVideos = false;
|
|
||||||
this.siteDisabled = false;
|
|
||||||
this.videos = [];
|
|
||||||
this.settings = settings;
|
|
||||||
this.actionHandlerInitQueue = [];
|
|
||||||
|
|
||||||
this.lastUrl = window.location.href;
|
|
||||||
this.extensionMode = extensionMode;
|
|
||||||
this.readOnly = readOnly;
|
|
||||||
|
|
||||||
|
|
||||||
if (comms){
|
|
||||||
this.comms = comms;
|
|
||||||
}
|
|
||||||
|
|
||||||
// request inject css immediately
|
|
||||||
try {
|
|
||||||
const playerStyleString = this.settings.active.sites[window.location.host].css.replace('\\n', '');
|
|
||||||
this.comms.sendMessage({
|
|
||||||
cmd: 'inject-css',
|
|
||||||
cssString: playerStyleString
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
// do nothing. It's ok if there's no special settings for the player element
|
|
||||||
}
|
|
||||||
|
|
||||||
this.rescan(RescanReason.PERIODIC);
|
|
||||||
this.scheduleUrlCheck();
|
|
||||||
|
|
||||||
this.currentZoomScale = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
injectCss(cssString) {
|
|
||||||
this.comms.sendMessage({
|
|
||||||
cmd: 'inject-css',
|
|
||||||
cssString: cssString
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ejectCss(cssString) {
|
|
||||||
this.comms.sendMessage({
|
|
||||||
cmd: 'eject-css',
|
|
||||||
cssString: cssString
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
replaceCss(oldCssString, newCssString) {
|
|
||||||
this.comms.sendMessage({
|
|
||||||
cmd: 'replace-css',
|
|
||||||
newCssString,
|
|
||||||
oldCssString
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy() {
|
|
||||||
this.logger.log('info', ['debug', 'init'], "[PageInfo::destroy] destroying all videos!")
|
|
||||||
if(this.rescanTimer){
|
|
||||||
clearTimeout(this.rescanTimer);
|
|
||||||
}
|
|
||||||
for (var video of this.videos) {
|
|
||||||
try {
|
|
||||||
this.comms.unregisterVideo(video.id)
|
|
||||||
video.destroy();
|
|
||||||
} catch (e) {
|
|
||||||
this.logger.log('error', ['debug', 'init'], '[PageInfo::destroy] unable to destroy video! Error:', e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
playerStyleString = this.settings.active.sites[window.location.host].css;
|
|
||||||
if (playerStyleString) {
|
|
||||||
this.comms.sendMessage({
|
|
||||||
cmd: 'remove-css',
|
|
||||||
cssString: playerStyleString
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// do nothing. It's ok if there's no special settings for the player element
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
reset() {
|
|
||||||
for(var video of this.videos) {
|
|
||||||
video.destroy();
|
|
||||||
}
|
|
||||||
this.rescan(RescanReason.MANUAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
initMouseActionHandler(videoData) {
|
|
||||||
if (this.actionHandler) {
|
|
||||||
this.actionHandler.registerHandleMouse(videoData);
|
|
||||||
} else {
|
|
||||||
this.actionHandlerInitQueue.push(videoData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setActionHandler(actionHandler) {
|
|
||||||
this.actionHandler = actionHandler;
|
|
||||||
for (var item of this.actionHandlerInitQueue) {
|
|
||||||
this.actionHandler.registerHandleMouse(item);
|
|
||||||
}
|
|
||||||
this.actionHandlerInitQueue = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
getVideos(host) {
|
|
||||||
if (this.settings.active.sites[host]
|
|
||||||
&& this.settings.active.sites[host].DOM
|
|
||||||
&& this.settings.active.sites[host].DOM.video
|
|
||||||
&& this.settings.active.sites[host].DOM.video.manual
|
|
||||||
&& this.settings.active.sites[host].DOM.video.querySelector){
|
|
||||||
const videos = document.querySelectorAll(this.settings.active.sites[host].DOM.video.querySelector);
|
|
||||||
|
|
||||||
if (videos.length) {
|
|
||||||
return videos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return document.getElementsByTagName('video');
|
|
||||||
}
|
|
||||||
|
|
||||||
hasVideo() {
|
|
||||||
return this.readOnly ? this.hasVideos : this.videos.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
rescan(rescanReason){
|
|
||||||
const oldVideoCount = this.videos.length;
|
|
||||||
|
|
||||||
try{
|
|
||||||
var vids = this.getVideos(window.location.host);
|
|
||||||
|
|
||||||
if(!vids || vids.length == 0){
|
|
||||||
this.hasVideos = false;
|
|
||||||
|
|
||||||
if(rescanReason == RescanReason.PERIODIC){
|
|
||||||
this.logger.log('info', 'videoRescan', "[PageInfo::rescan] Scheduling normal rescan.")
|
|
||||||
this.scheduleRescan(RescanReason.PERIODIC);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// add new videos
|
|
||||||
this.hasVideos = false;
|
|
||||||
var videoExists = false;
|
|
||||||
var video, v;
|
|
||||||
|
|
||||||
for (video of vids) {
|
|
||||||
// če najdemo samo en video z višino in širino, to pomeni, da imamo na strani veljavne videe
|
|
||||||
// če trenutni video nima definiranih teh vrednostih, preskočimo vse nadaljnja preverjanja
|
|
||||||
// <===[:::::::]===>
|
|
||||||
// if we find even a single video with width and height, that means the page has valid videos
|
|
||||||
// if video lacks either of the two properties, we skip all further checks cos pointless
|
|
||||||
if(video.offsetWidth && video.offsetHeight){
|
|
||||||
this.hasVideos = true;
|
|
||||||
|
|
||||||
if (this.readOnly) {
|
|
||||||
// in lite mode, we're done. This is all the info we want, but we want to actually start doing
|
|
||||||
// things that interfere with the website. We still want to be runnig a rescan, tho.
|
|
||||||
|
|
||||||
if(rescanReason == RescanReason.PERIODIC){
|
|
||||||
this.scheduleRescan(RescanReason.PERIODIC);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
videoExists = false;
|
|
||||||
|
|
||||||
for (v of this.videos) {
|
|
||||||
if (v.destroyed) {
|
|
||||||
continue; //TODO: if destroyed video is same as current video, copy aspect ratio settings to current video
|
|
||||||
}
|
|
||||||
|
|
||||||
if (v.video == video) {
|
|
||||||
videoExists = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (videoExists) {
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
this.logger.log('info', 'videoRescan', "[PageInfo::rescan] found new video candidate:", video, "NOTE:: Video initialization starts here:\n--------------------------------\n")
|
|
||||||
|
|
||||||
try {
|
|
||||||
v = new VideoData(video, this.settings, this);
|
|
||||||
if (!v.invalid) {
|
|
||||||
v.initArDetection();
|
|
||||||
} else {
|
|
||||||
this.logger.log('error', 'debug', 'Video is invalid. Aard not started.', video);
|
|
||||||
}
|
|
||||||
this.videos.push(v);
|
|
||||||
} catch (e) {
|
|
||||||
this.logger.log('error', 'debug', "rescan error: failed to initialize videoData. Skipping this video.",e);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.log('info', 'videoRescan', "END VIDEO INITIALIZATION\n\n\n-------------------------------------\nvideos[] is now this:", this.videos,"\n\n\n\n\n\n\n\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.removeDestroyed();
|
|
||||||
|
|
||||||
// če smo ostali brez videev, potem odregistriraj stran.
|
|
||||||
// če nismo ostali brez videev, potem registriraj stran.
|
|
||||||
//
|
|
||||||
// if we're left withotu videos on the current page, we unregister the page.
|
|
||||||
// if we have videos, we call register.
|
|
||||||
if (this.comms) {
|
|
||||||
if (this.videos.length != oldVideoCount) { // only if number of videos changed, tho
|
|
||||||
if (this.videos.length > 0) {
|
|
||||||
this.comms.registerVideo({host: window.location.host, location: window.location});
|
|
||||||
} else {
|
|
||||||
this.comms.unregisterVideo({host: window.location.host, location: window.location});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch(e) {
|
|
||||||
// če pride do zajeba, potem lahko domnevamo da na strani ni nobenega videa. Uničimo vse objekte videoData
|
|
||||||
// da preprečimo večkratno inicializacijo. Če smo se z našim ugibom zmotili, potem se bodo vsi videi ponovno
|
|
||||||
// našli ob naslednjem preiskovanju
|
|
||||||
//
|
|
||||||
// if we encounter a fuckup, we can assume that no videos were found on the page. We destroy all videoData
|
|
||||||
// objects to prevent multiple initalization (which happened, but I don't know why). No biggie if we destroyed
|
|
||||||
// videoData objects in error — they'll be back in the next rescan
|
|
||||||
this.logger.log('error', 'debug', "rescan error: — destroying all videoData objects",e);
|
|
||||||
for (const v of this.videos) {
|
|
||||||
v.destroy();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(rescanReason == RescanReason.PERIODIC){
|
|
||||||
this.scheduleRescan(RescanReason.PERIODIC);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
removeDestroyed(){
|
|
||||||
this.videos = this.videos.filter( vid => vid.destroyed === false);
|
|
||||||
}
|
|
||||||
|
|
||||||
scheduleRescan(rescanReason){
|
|
||||||
if(rescanReason != RescanReason.PERIODIC){
|
|
||||||
this.rescan(rescanReason);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try{
|
|
||||||
if(this.rescanTimer){
|
|
||||||
clearTimeout(this.rescanTimer);
|
|
||||||
}
|
|
||||||
|
|
||||||
var ths = this;
|
|
||||||
|
|
||||||
this.rescanTimer = setTimeout(function(rescanReason){
|
|
||||||
ths.rescanTimer = null;
|
|
||||||
ths.rescan(rescanReason);
|
|
||||||
ths = null;
|
|
||||||
}, this.settings.active.pageInfo.timeouts.rescan, RescanReason.PERIODIC)
|
|
||||||
} catch(e) {
|
|
||||||
this.logger.log('error', 'debug', "[PageInfo::scheduleRescan] scheduling rescan failed. Here's why:",e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
scheduleUrlCheck() {
|
|
||||||
try{
|
|
||||||
if(this.urlCheckTimer){
|
|
||||||
clearTimeout(this.urlCheckTimer);
|
|
||||||
}
|
|
||||||
|
|
||||||
var ths = this;
|
|
||||||
|
|
||||||
this.urlCheckTimer = setTimeout(function(){
|
|
||||||
ths.urlCheckTimer = null;
|
|
||||||
ths.ghettoUrlCheck();
|
|
||||||
ths = null;
|
|
||||||
}, this.settings.active.pageInfo.timeouts.urlCheck)
|
|
||||||
} catch(e){
|
|
||||||
this.logger.log('error', 'debug', "[PageInfo::scheduleUrlCheck] scheduling URL check failed. Here's why:",e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ghettoUrlCheck() {
|
|
||||||
if (this.lastUrl != window.location.href){
|
|
||||||
this.logger.log('error', 'videoRescan', "[PageInfo::ghettoUrlCheck] URL has changed. Triggering a rescan!");
|
|
||||||
|
|
||||||
this.rescan(RescanReason.URL_CHANGE);
|
|
||||||
this.lastUrl = window.location.href;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.scheduleUrlCheck();
|
|
||||||
}
|
|
||||||
|
|
||||||
initArDetection(playingOnly){
|
|
||||||
if (playingOnly) {
|
|
||||||
for(var vd of this.videos){
|
|
||||||
if(vd.isPlaying()) {
|
|
||||||
vd.initArDetection();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
for(var vd of this.videos){
|
|
||||||
vd.initArDetection();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// to je treba klicat ob menjavi zavihkov
|
|
||||||
// these need to be called on tab switch
|
|
||||||
pauseProcessing(playingOnly){
|
|
||||||
if (playingOnly) {
|
|
||||||
for(var vd of this.videos){
|
|
||||||
if (vd.isPlaying()) {
|
|
||||||
vd.pause();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for(var vd of this.videos){
|
|
||||||
vd.pause();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resumeProcessing(resumeAutoar = false, playingOnly = false){
|
|
||||||
if (playingOnly) {
|
|
||||||
for(var vd of this.videos){
|
|
||||||
if (vd.isPlaying()) {
|
|
||||||
vd.resume();
|
|
||||||
if(resumeAutoar){
|
|
||||||
vd.resumeAutoAr();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for(var vd of this.videos){
|
|
||||||
vd.resume();
|
|
||||||
if(resumeAutoar){
|
|
||||||
vd.resumeAutoAr();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
startArDetection(playingOnly){
|
|
||||||
if (Debug.debug) {
|
|
||||||
this.logger.log('info', 'debug', '[PageInfo::startArDetection()] starting automatic ar detection!')
|
|
||||||
}
|
|
||||||
if (playingOnly) {
|
|
||||||
for(var vd of this.videos){
|
|
||||||
if (video.isPlaying()) {
|
|
||||||
vd.startArDetection();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for(var vd of this.videos){
|
|
||||||
vd.startArDetection();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stopArDetection(playingOnly){
|
|
||||||
if (playingOnly) {
|
|
||||||
for(var vd of this.videos){
|
|
||||||
if (vd.isPlaying()) {
|
|
||||||
vd.stopArDetection();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for(var vd of this.videos){
|
|
||||||
vd.stopArDetection();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setAr(ar, playingOnly){
|
|
||||||
this.logger.log('info', 'debug', '[PageInfo::setAr] aspect ratio:', ar, "playing only?", playingOnly)
|
|
||||||
|
|
||||||
if (ar.type !== AspectRatio.Automatic) {
|
|
||||||
this.stopArDetection(playingOnly);
|
|
||||||
} else {
|
|
||||||
this.logger.log('info', 'debug', '[PageInfo::setAr] aspect ratio is auto');
|
|
||||||
|
|
||||||
try {
|
|
||||||
for (var vd of this.videos) {
|
|
||||||
if (!playingOnly || vd.isPlaying()) {
|
|
||||||
vd.resetLastAr();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
this.logger.log('error', 'debug', "???", e);
|
|
||||||
}
|
|
||||||
this.initArDetection(playingOnly);
|
|
||||||
this.startArDetection(playingOnly);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: find a way to only change aspect ratio for one video
|
|
||||||
if (ar === AspectRatio.Reset) {
|
|
||||||
for (var vd of this.videos) {
|
|
||||||
if (!playingOnly || vd.isPlaying()) {
|
|
||||||
vd.resetAr();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (var vd of this.videos) {
|
|
||||||
if (!playingOnly || vd.isPlaying()) {
|
|
||||||
vd.setAr(ar)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setVideoAlignment(videoAlignment, playingOnly) {
|
|
||||||
if (playingOnly) {
|
|
||||||
for(var vd of this.videos) {
|
|
||||||
if (vd.isPlaying()) {
|
|
||||||
vd.setVideoAlignment(videoAlignment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for(var vd of this.videos) {
|
|
||||||
vd.setVideoAlignment(videoAlignment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setPanMode(mode, playingOnly) {
|
|
||||||
if (playingOnly) {
|
|
||||||
for(var vd of this.videos) {
|
|
||||||
if (vd.isPlaying()) {
|
|
||||||
vd.setPanMode(mode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for(var vd of this.videos) {
|
|
||||||
vd.setPanMode(mode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
restoreAr(playingOnly) {
|
|
||||||
if (playingOnly) {
|
|
||||||
for(var vd of this.videos){
|
|
||||||
if (vd.isPlaying()) {
|
|
||||||
vd.restoreAr();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for(var vd of this.videos){
|
|
||||||
vd.restoreAr();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setStretchMode(sm, playingOnly){
|
|
||||||
// TODO: find a way to only change aspect ratio for one video
|
|
||||||
|
|
||||||
if (playingOnly) {
|
|
||||||
for(var vd of this.videos){
|
|
||||||
if (vd.isPlaying()) {
|
|
||||||
vd.setStretchMode(sm)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for(var vd of this.videos){
|
|
||||||
vd.setStretchMode(sm)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setZoom(zoomLevel, no_announce, playingOnly) {
|
|
||||||
if (playingOnly) {
|
|
||||||
for(var vd of this.videos) {
|
|
||||||
if (vd.isPlaying()) {
|
|
||||||
vd.setZoom(zoomLevel, no_announce);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for(var vd of this.videos) {
|
|
||||||
vd.setZoom(zoomLevel, no_announce);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
zoomStep(step, playingOnly) {
|
|
||||||
for(var vd of this.videos){
|
|
||||||
if (!playingOnly || vd.isPlaying()) {
|
|
||||||
vd.zoomStep(step);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
markPlayer(name, color) {
|
|
||||||
for (var vd of this.videos) {
|
|
||||||
vd.markPlayer(name,color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
unmarkPlayer() {
|
|
||||||
for (var vd of this.videos) {
|
|
||||||
vd.unmarkPlayer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
announceZoom(scale) {
|
|
||||||
if (this.announceZoomTimeout) {
|
|
||||||
clearTimeout(this.announceZoom);
|
|
||||||
}
|
|
||||||
this.currentZoomScale = scale;
|
|
||||||
const ths = this;
|
|
||||||
this.announceZoomTimeout = setTimeout(() => ths.comms.announceZoom(scale), this.settings.active.zoom.announceDebounce);
|
|
||||||
}
|
|
||||||
|
|
||||||
setManualTick(manualTick) {
|
|
||||||
for(var vd of this.videos) {
|
|
||||||
vd.setManualTick();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tick() {
|
|
||||||
for(var vd of this.videos) {
|
|
||||||
vd.tick();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sendPerformanceUpdate(performanceUpdate) {
|
|
||||||
if(this.comms) {
|
|
||||||
this.comms.sendPerformanceUpdate(performanceUpdate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
requestCurrentZoom() {
|
|
||||||
this.comms.announceZoom(this.currentZoomScale);
|
|
||||||
}
|
|
||||||
|
|
||||||
setKeyboardShortcutsEnabled(state) {
|
|
||||||
this.actionHandler.setKeybordLocal(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PageInfo;
|
|
||||||
@ -1,407 +0,0 @@
|
|||||||
import Debug from '../../conf/Debug';
|
|
||||||
import ExtensionMode from '../../../common/enums/extension-mode.enum'
|
|
||||||
import AspectRatio from '../../../common/enums/aspect-ratio.enum';
|
|
||||||
|
|
||||||
if(Debug.debug)
|
|
||||||
console.log("Loading: PlayerData.js");
|
|
||||||
|
|
||||||
/* sprejme <video> tag (element) in seznam imen, ki se lahko pojavijo v razredih oz. id staršev.
|
|
||||||
// vrne dimenzije predvajalnika (širina, višina)
|
|
||||||
//
|
|
||||||
// Na youtube v theater mode je razširitev rahlo pokvarjena. Video tag ostane večji od predvajalnika, ko se zapusti
|
|
||||||
// celozaslonski način. Ta funkcija skuša to težavo rešiti tako, da poišče element predvajalnika, ki je zavit okoli videa.
|
|
||||||
//
|
|
||||||
// Funkcija izkorišča lastnost, da bi načeloma moral biti vsak zunanji element večji od notranjega. Najmanjši element od
|
|
||||||
// <video> značke pa do korena drevesa bi tako moral biti predvajalnik.
|
|
||||||
//
|
|
||||||
// Če je podan seznam imen, potem funkcija vrne dimenzije prvega elementa, ki v id oz. razredu vsebuje katerokoli ime iz seznama
|
|
||||||
//
|
|
||||||
// | EN |
|
|
||||||
//
|
|
||||||
// accepts <video> tag (element) and list of names that can appear in id or class
|
|
||||||
// returns player dimensions (width, height)
|
|
||||||
//
|
|
||||||
// Theater mode is mildly broken on youtube. <video> tag remains bigger than the player after leaving the fullscreen mode, and
|
|
||||||
// there's nothing we can do about that. This function aims to solve the problem by finding the player element that's wrapped around
|
|
||||||
// the <video> tag.
|
|
||||||
//
|
|
||||||
// In general, an outer tag should be bigger than the inner tag. Therefore the smallest element between <video> tag and the document
|
|
||||||
// root should be the player.
|
|
||||||
//
|
|
||||||
// If list of names is provided, the function returns dimensions of the first element that contains any name from the list in either
|
|
||||||
// id or class.
|
|
||||||
*/
|
|
||||||
|
|
||||||
class PlayerData {
|
|
||||||
constructor(videoData) {
|
|
||||||
try {
|
|
||||||
this.logger = videoData.logger;
|
|
||||||
this.videoData = videoData;
|
|
||||||
this.video = videoData.video;
|
|
||||||
this.settings = videoData.settings;
|
|
||||||
this.extensionMode = videoData.extensionMode;
|
|
||||||
this.invalid = false;
|
|
||||||
this.element = this.getPlayer();
|
|
||||||
this.dimensions = undefined;
|
|
||||||
this.overlayNode = undefined;
|
|
||||||
|
|
||||||
// this happens when we don't find a matching player element
|
|
||||||
if (!this.element) {
|
|
||||||
this.invalid = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.extensionMode === ExtensionMode.Enabled) {
|
|
||||||
this.checkPlayerSizeChange();
|
|
||||||
}
|
|
||||||
this.startChangeDetection();
|
|
||||||
} catch (e) {
|
|
||||||
console.error('[Ultrawidify::PlayerData::ctor] There was an error setting up player data. You should be never seeing this message. Error:', e);
|
|
||||||
this.invalid = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async sleep(timeout) {
|
|
||||||
return new Promise( (resolve, reject) => setTimeout(() => resolve(), timeout));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static isFullScreen(){
|
|
||||||
return ( window.innerHeight == window.screen.height && window.innerWidth == window.screen.width);
|
|
||||||
}
|
|
||||||
|
|
||||||
// player size observer may not be strictly necessary here
|
|
||||||
onPlayerDimensionsChanged(mutationList, observer, context) {
|
|
||||||
if (context.checkPlayerSizeChange()) {
|
|
||||||
context.videoData.resizer.restore();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
start(){
|
|
||||||
this.startChangeDetection();
|
|
||||||
}
|
|
||||||
|
|
||||||
stop(){
|
|
||||||
this.halted = true;
|
|
||||||
this.stopChangeDetection();
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy() {
|
|
||||||
this.stopChangeDetection();
|
|
||||||
this.destroyOverlay();
|
|
||||||
}
|
|
||||||
|
|
||||||
startChangeDetection(){
|
|
||||||
if (this.invalid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const ths = this;
|
|
||||||
this.observer = new MutationObserver((m,o) => this.onPlayerDimensionsChanged(m,o,ths));
|
|
||||||
|
|
||||||
const observerConf = {
|
|
||||||
attributes: true,
|
|
||||||
// attributeFilter: ['style', 'class'],
|
|
||||||
attributeOldValue: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.observer.observe(this.element, observerConf);
|
|
||||||
} catch (e) {
|
|
||||||
console.error("failed to set observer",e )
|
|
||||||
}
|
|
||||||
// legacy mode still exists, but acts as a fallback for observers and is triggered less
|
|
||||||
// frequently in order to avoid too many pointless checks
|
|
||||||
this.legacyChangeDetection();
|
|
||||||
}
|
|
||||||
|
|
||||||
async legacyChangeDetection() {
|
|
||||||
while (!this.halted) {
|
|
||||||
await this.sleep(1000);
|
|
||||||
try {
|
|
||||||
if (this.checkPlayerSizeChange()) {
|
|
||||||
this.videoData.resizer.restore();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('[playerdata::legacycd] this message is pretty high on the list of messages you shouldnt see', e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stopChangeDetection(){
|
|
||||||
this.observer.disconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
makeOverlay() {
|
|
||||||
if (!this.overlayNode) {
|
|
||||||
this.destroyOverlay();
|
|
||||||
}
|
|
||||||
|
|
||||||
var overlay = document.createElement('div');
|
|
||||||
overlay.style.width = '100%';
|
|
||||||
overlay.style.height = '100%';
|
|
||||||
overlay.style.position = 'absolute';
|
|
||||||
overlay.style.top = '0';
|
|
||||||
overlay.style.left = '0';
|
|
||||||
overlay.style.zIndex = '1000000000';
|
|
||||||
overlay.style.pointerEvents = 'none';
|
|
||||||
|
|
||||||
this.overlayNode = overlay;
|
|
||||||
this.element.appendChild(overlay);
|
|
||||||
}
|
|
||||||
|
|
||||||
destroyOverlay() {
|
|
||||||
if(this.playerIdElement) {
|
|
||||||
this.playerIdElement.remove();
|
|
||||||
this.playerIdElement = undefined;
|
|
||||||
}
|
|
||||||
if (this.overlayNode) {
|
|
||||||
this.overlayNode.remove();
|
|
||||||
this.overlayNode = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
markPlayer(name, color) {
|
|
||||||
if (!this.overlayNode) {
|
|
||||||
this.makeOverlay();
|
|
||||||
}
|
|
||||||
if (this.playerIdElement) {
|
|
||||||
this.playerIdElement.remove();
|
|
||||||
}
|
|
||||||
this.playerIdElement = document.createElement('div');
|
|
||||||
this.playerIdElement.innerHTML = `<div style="background-color: ${color}; color: #fff; position: absolute; top: 0; left: 0">${name}</div>`;
|
|
||||||
|
|
||||||
this.overlayNode.appendChild(this.playerIdElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
unmarkPlayer() {
|
|
||||||
this.logger.log('info', 'debug', "[PlayerData::unmarkPlayer] unmarking player!")
|
|
||||||
if (this.playerIdElement) {
|
|
||||||
this.playerIdElement.remove();
|
|
||||||
}
|
|
||||||
this.playerIdElement = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
collectionHas(collection, element) {
|
|
||||||
for (let i = 0, len = collection.length; i < len; i++) {
|
|
||||||
if (collection[i] == element) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
getPlayer() {
|
|
||||||
const host = window.location.host;
|
|
||||||
let element = this.video.parentNode;
|
|
||||||
const videoWidth = this.video.offsetWidth, videoHeight = this.video.offsetHeight;
|
|
||||||
const elementQ = [];
|
|
||||||
let scorePenalty = 0;
|
|
||||||
let score;
|
|
||||||
|
|
||||||
try {
|
|
||||||
if(! element ){
|
|
||||||
this.logger.log('info', 'debug', "[PlayerDetect::_pd_getPlayer] element is not valid, doing nothing.", element)
|
|
||||||
if(this.element) {
|
|
||||||
const ths = this;
|
|
||||||
}
|
|
||||||
this.element = undefined;
|
|
||||||
this.dimensions = undefined;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.settings.active.sites[host]
|
|
||||||
&& this.settings.active.sites[host].DOM
|
|
||||||
&& this.settings.active.sites[host].DOM.player
|
|
||||||
&& this.settings.active.sites[host].DOM.player.manual) {
|
|
||||||
if (this.settings.active.sites[host].DOM.player.useRelativeAncestor
|
|
||||||
&& this.settings.active.sites[host].DOM.player.videoAncestor) {
|
|
||||||
|
|
||||||
let parentsLeft = this.settings.active.sites[host].DOM.player.videoAncestor - 1;
|
|
||||||
while (parentsLeft --> 0) {
|
|
||||||
element = element.parentNode;
|
|
||||||
}
|
|
||||||
if (element) {
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
} else if (this.settings.active.sites[host].DOM.player.querySelectors) {
|
|
||||||
const allSelectors = document.querySelectorAll(this.settings.active.sites[host].DOM.player.querySelectors);
|
|
||||||
// actually we'll also score this branch in a similar way we score the regular, auto branch
|
|
||||||
while (element) {
|
|
||||||
|
|
||||||
// Let's see how this works
|
|
||||||
if (this.collectionHas(allSelectors, element)) {
|
|
||||||
score = 100; // every matching element gets a baseline 100 points
|
|
||||||
|
|
||||||
// elements that match the size get a hefty bonus
|
|
||||||
if ( (element.offsetWidth >= videoWidth && this.equalish(element.offsetHeight, videoHeight, 2))
|
|
||||||
|| (element.offsetHeight >= videoHeight && this.equalish(element.offsetWidth, videoHeight, 2))) {
|
|
||||||
score += 75;
|
|
||||||
}
|
|
||||||
|
|
||||||
// elements farther away from the video get a penalty
|
|
||||||
score -= (scorePenalty++) * 20;
|
|
||||||
|
|
||||||
// push the element on the queue/stack:
|
|
||||||
elementQ.push({
|
|
||||||
score: score,
|
|
||||||
element: element,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
element = element.parentNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (elementQ.length) {
|
|
||||||
// return element with biggest score
|
|
||||||
// if video player has not been found, proceed to automatic detection
|
|
||||||
return elementQ.sort( (a,b) => b.score - a.score)[0].element;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// try to find element the old fashioned way
|
|
||||||
|
|
||||||
while (element){
|
|
||||||
// odstranimo čudne elemente, ti bi pokvarili zadeve
|
|
||||||
// remove weird elements, those would break our stuff
|
|
||||||
if ( element.offsetWidth == 0 || element.offsetHeight == 0){
|
|
||||||
element = element.parentNode;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// element je player, če je ena stranica enako velika kot video, druga pa večja ali enaka.
|
|
||||||
// za enakost dovolimo mala odstopanja
|
|
||||||
// element is player, if one of the sides is as long as the video and the other bigger (or same)
|
|
||||||
// we allow for tiny variations when checking for equality
|
|
||||||
if ( (element.offsetWidth >= videoWidth && this.equalish(element.offsetHeight, videoHeight, 2))
|
|
||||||
|| (element.offsetHeight >= videoHeight && this.equalish(element.offsetWidth, videoHeight, 2))) {
|
|
||||||
|
|
||||||
// todo — in case the match is only equalish and not exact, take difference into account when
|
|
||||||
// calculating score
|
|
||||||
|
|
||||||
score = 100;
|
|
||||||
|
|
||||||
|
|
||||||
if (element.id.indexOf('player') !== -1) { // prefer elements with 'player' in id
|
|
||||||
score += 75;
|
|
||||||
}
|
|
||||||
// this has only been observed on steam
|
|
||||||
if (element.id.indexOf('movie') !== -1) {
|
|
||||||
score += 75;
|
|
||||||
}
|
|
||||||
if (element.classList.toString().indexOf('player') !== -1) { // prefer elements with 'player' in classlist, but a bit less than id
|
|
||||||
score += 50;
|
|
||||||
}
|
|
||||||
score -= scorePenalty++; // prefer elements closer to <video>
|
|
||||||
|
|
||||||
elementQ.push({
|
|
||||||
element: element,
|
|
||||||
score: score,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
element = element.parentNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (elementQ.length) {
|
|
||||||
// return element with biggest score
|
|
||||||
return elementQ.sort( (a,b) => b.score - a.score)[0].element;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if no candidates were found, something is obviously very, _very_ wrong.
|
|
||||||
// we return nothing. Player will be marked as invalid and setup will stop.
|
|
||||||
// VideoData should check for that before starting anything.
|
|
||||||
this.logger.log('warn', 'debug', '[PlayerData::getPlayer] no matching player was found for video', this.video, 'Extension cannot work on this site.');
|
|
||||||
return;
|
|
||||||
} catch (e) {
|
|
||||||
this.logger.log('crit', 'debug', '[PlayerData::getPlayer] something went wrong while detecting player:', e, 'Shutting down extension for this page');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
equalish(a,b, tolerance) {
|
|
||||||
return a > b - tolerance && a < b + tolerance;
|
|
||||||
}
|
|
||||||
|
|
||||||
forceRefreshPlayerElement() {
|
|
||||||
this.checkPlayerSizeChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
checkPlayerSizeChange(){
|
|
||||||
// this 'if' is just here for debugging — real code starts later. It's safe to collapse and
|
|
||||||
// ignore the contents of this if (unless we need to change how logging works)
|
|
||||||
if (this.logger.canLog('debug')){
|
|
||||||
if (!this.dimensions) {
|
|
||||||
|
|
||||||
} else if (this.dimensions && this.dimensions.fullscreen){
|
|
||||||
if(! PlayerData.isFullScreen()){
|
|
||||||
this.logger.log('info', 'debug', "[PlayerDetect] player size changed. reason: exited fullscreen");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(! this.element) {
|
|
||||||
this.logger.log('info', 'playerDetect', "[PlayerDetect] player element isn't defined");
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( this.element && this.dimensions &&
|
|
||||||
( this.dimensions.width != this.element.offsetWidth ||
|
|
||||||
this.dimensions.height != this.element.offsetHeight )
|
|
||||||
) {
|
|
||||||
this.logger.log('info', 'debug', "[PlayerDetect] player size changed. reason: dimension change. Old dimensions?", this.dimensions.width, this.dimensions.height, "new dimensions:", this.element.offsetWidth, this.element.offsetHeight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// if size doesn't match, update & return true
|
|
||||||
if (!this.dimensions
|
|
||||||
|| this.dimensions.width != this.element.offsetWidth
|
|
||||||
|| this.dimensions.height != this.element.offsetHeight ){
|
|
||||||
|
|
||||||
const isFullScreen = PlayerData.isFullScreen();
|
|
||||||
|
|
||||||
if (isFullScreen) {
|
|
||||||
this.dimensions = {
|
|
||||||
width: window.innerWidth,
|
|
||||||
height: window.innerHeight,
|
|
||||||
fullscreen: true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.dimensions = {
|
|
||||||
width: this.element.offsetWidth,
|
|
||||||
height: this.element.offsetHeight,
|
|
||||||
fullscreen: isFullScreen
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
checkFullscreenChange() {
|
|
||||||
const isFs = PlayerData.isFullScreen();
|
|
||||||
|
|
||||||
if (this.dimensions) {
|
|
||||||
if (this.dimensions.fullscreen != isFs) {
|
|
||||||
this.dimensions = {
|
|
||||||
fullscreen: isFs,
|
|
||||||
width: isFs ? screen.width : this.video.offsetWidth,
|
|
||||||
height: isFs ? screen.height : this.video.offsetHeight
|
|
||||||
};
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.log('info', 'debug', "[PlayerData::checkFullscreenChange] this.dimensions is not defined. Assuming fs change happened and setting default values.")
|
|
||||||
|
|
||||||
this.dimensions = {
|
|
||||||
fullscreen: isFs,
|
|
||||||
width: isFs ? screen.width : this.video.offsetWidth,
|
|
||||||
height: isFs ? screen.height : this.video.offsetHeight
|
|
||||||
};
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PlayerData;
|
|
||||||
@ -1,413 +0,0 @@
|
|||||||
import Debug from '../../conf/Debug';
|
|
||||||
import PlayerData from './PlayerData';
|
|
||||||
import Resizer from '../video-transform/Resizer';
|
|
||||||
import ArDetector from '../ar-detect/ArDetector';
|
|
||||||
|
|
||||||
class VideoData {
|
|
||||||
|
|
||||||
constructor(video, settings, pageInfo){
|
|
||||||
this.vdid = (Math.random()*100).toFixed();
|
|
||||||
this.logger = pageInfo.logger;
|
|
||||||
this.arSetupComplete = false;
|
|
||||||
this.video = video;
|
|
||||||
this.destroyed = false;
|
|
||||||
this.settings = settings;
|
|
||||||
this.pageInfo = pageInfo;
|
|
||||||
this.extensionMode = pageInfo.extensionMode;
|
|
||||||
|
|
||||||
this.userCssClassName = `uw-fuck-you-and-do-what-i-tell-you_${this.vdid}`;
|
|
||||||
|
|
||||||
// We only init observers once player is confirmed valid
|
|
||||||
const observerConf = {
|
|
||||||
attributes: true,
|
|
||||||
// attributeFilter: ['style', 'class'],
|
|
||||||
attributeOldValue: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// POZOR: VRSTNI RED JE POMEMBEN (arDetect mora bit zadnji)
|
|
||||||
// NOTE: ORDERING OF OBJ INITIALIZATIONS IS IMPORTANT (arDetect needs to go last)
|
|
||||||
this.player = new PlayerData(this);
|
|
||||||
if (this.player.invalid) {
|
|
||||||
this.invalid = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ths = this;
|
|
||||||
this.observer = new MutationObserver( (m, o) => this.onVideoDimensionsChanged(m, o, ths));
|
|
||||||
this.observer.observe(video, observerConf);
|
|
||||||
|
|
||||||
this.dimensions = {
|
|
||||||
width: this.video.offsetWidth,
|
|
||||||
height: this.video.offsetHeight,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.resizer = new Resizer(this);
|
|
||||||
this.arDetector = new ArDetector(this); // this starts Ar detection. needs optional parameter that prevets ardetdctor from starting
|
|
||||||
// player dimensions need to be in:
|
|
||||||
// this.player.dimensions
|
|
||||||
|
|
||||||
// apply default align and stretch
|
|
||||||
this.logger.log('info', 'debug', "%c[VideoData::ctor] Initial resizer reset!", {background: '#afd', color: '#132'});
|
|
||||||
this.resizer.reset();
|
|
||||||
|
|
||||||
this.logger.log('info', ['debug', 'init'], '[VideoData::ctor] Created videoData with vdid', this.vdid, '\nextension mode:', this.extensionMode)
|
|
||||||
|
|
||||||
this.pageInfo.initMouseActionHandler(this);
|
|
||||||
this.video.classList.add(this.userCssClassName); // this also needs to be applied BEFORE we initialize resizer!
|
|
||||||
|
|
||||||
// start fallback video/player size detection
|
|
||||||
this.fallbackChangeDetection();
|
|
||||||
}
|
|
||||||
|
|
||||||
async fallbackChangeDetection() {
|
|
||||||
while (!this.destroyed && !this.invalid) {
|
|
||||||
await this.sleep(500);
|
|
||||||
this.validateVideoOffsets();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async sleep(timeout) {
|
|
||||||
return new Promise( (resolve, reject) => setTimeout(() => resolve(), timeout));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
onVideoDimensionsChanged(mutationList, observer, context) {
|
|
||||||
if (!mutationList || context.video === undefined) { // something's wrong
|
|
||||||
if (observer && context.video) {
|
|
||||||
observer.disconnect();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (let mutation of mutationList) {
|
|
||||||
if (mutation.type === 'attributes'
|
|
||||||
&& mutation.attributeName === 'class'
|
|
||||||
&& !context.video.classList.contains(this.userCssClassName) ) {
|
|
||||||
// force the page to include our class in classlist, if the classlist has been removed
|
|
||||||
// while classList.add() doesn't duplicate classes (does nothing if class is already added),
|
|
||||||
// we still only need to make sure we're only adding our class to classlist if it has been
|
|
||||||
// removed. classList.add() will _still_ trigger mutation (even if classlist wouldn't change).
|
|
||||||
// This is a problem because INFINITE RECURSION TIME, and we _really_ don't want that.
|
|
||||||
|
|
||||||
context.video.classList.add(this.userCssClassName);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// adding player observer taught us that if element size gets triggered by a class, then
|
|
||||||
// the 'style' attributes don't necessarily trigger. This means we also need to trigger
|
|
||||||
// restoreAr here, in case video size was changed this way
|
|
||||||
context.player.forceRefreshPlayerElement();
|
|
||||||
context.restoreAr();
|
|
||||||
|
|
||||||
// sometimes something fucky wucky happens and mutations aren't detected correctly, so we
|
|
||||||
// try to get around that
|
|
||||||
setTimeout( () => {
|
|
||||||
context.validateVideoOffsets();
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
validateVideoOffsets() {
|
|
||||||
// validate if current video still exists. If not, we destroy current object
|
|
||||||
try {
|
|
||||||
if (! document.body.contains(this.video)) {
|
|
||||||
this.destroy();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
}
|
|
||||||
// THIS BREAKS PANNING
|
|
||||||
const cs = window.getComputedStyle(this.video);
|
|
||||||
const pcs = window.getComputedStyle(this.player.element);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const transformMatrix = cs.transform.split(')')[0].split(',');
|
|
||||||
const translateX = +transformMatrix[4];
|
|
||||||
const translateY = +transformMatrix[5];
|
|
||||||
const vh = +(cs.height.split('px')[0]);
|
|
||||||
const vw = +(cs.width.split('px')[0]);
|
|
||||||
const ph = +(pcs.height.split('px')[0]);
|
|
||||||
const pw = +(pcs.width.split('px')[0]);
|
|
||||||
|
|
||||||
// TODO: check & account for panning and alignment
|
|
||||||
if (this.isWithin(vh, (ph - (translateY / 2)), 2)
|
|
||||||
&& this.isWithin(vw, (pw - (translateX / 2)), 2)) {
|
|
||||||
} else {
|
|
||||||
this.player.forceRefreshPlayerElement();
|
|
||||||
this.restoreAr();
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch(e) {
|
|
||||||
// do nothing on fail
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
isWithin(a, b, diff) {
|
|
||||||
return a < b + diff && a > b - diff
|
|
||||||
}
|
|
||||||
|
|
||||||
firstTimeArdInit(){
|
|
||||||
if(this.destroyed || this.invalid) {
|
|
||||||
// throw {error: 'VIDEO_DATA_DESTROYED', data: {videoData: this}};
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(! this.arSetupComplete){
|
|
||||||
this.arDetector = new ArDetector(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
initArDetection() {
|
|
||||||
if(this.destroyed || this.invalid) {
|
|
||||||
// throw {error: 'VIDEO_DATA_DESTROYED', data: {videoData: this}};
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(this.arDetector){
|
|
||||||
this.arDetector.init();
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
this.arDetector = new ArDetector(this);
|
|
||||||
this.arDetector.init();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
startArDetection() {
|
|
||||||
this.logger.log('info', 'debug', "[VideoData::startArDetection] starting AR detection")
|
|
||||||
if(this.destroyed || this.invalid) {
|
|
||||||
// throw {error: 'VIDEO_DATA_DESTROYED', data: {videoData: this}};
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(!this.arDetector) {
|
|
||||||
this.arDetector.init();
|
|
||||||
}
|
|
||||||
this.arDetector.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
rebootArDetection() {
|
|
||||||
if(this.destroyed || this.invalid) {
|
|
||||||
// throw {error: 'VIDEO_DATA_DESTROYED', data: {videoData: this}};
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.arDetector.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
stopArDetection() {
|
|
||||||
if (this.arDetector) {
|
|
||||||
this.arDetector.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy() {
|
|
||||||
this.logger.log('info', ['debug', 'init'], `[VideoData::destroy] <vdid:${this.vdid}> received destroy command`);
|
|
||||||
|
|
||||||
if (this.video) {
|
|
||||||
this.video.classList.remove(this.userCssClassName);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.pause();
|
|
||||||
this.destroyed = true;
|
|
||||||
try {
|
|
||||||
this.arDetector.stop();
|
|
||||||
this.arDetector.destroy();
|
|
||||||
} catch (e) {}
|
|
||||||
this.arDetector = undefined;
|
|
||||||
try {
|
|
||||||
this.resizer.destroy();
|
|
||||||
} catch (e) {}
|
|
||||||
this.resizer = undefined;
|
|
||||||
try {
|
|
||||||
this.player.destroy();
|
|
||||||
} catch (e) {}
|
|
||||||
try {
|
|
||||||
this.observer.disconnect();
|
|
||||||
} catch (e) {}
|
|
||||||
this.player = undefined;
|
|
||||||
this.video = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
pause(){
|
|
||||||
this.paused = true;
|
|
||||||
if(this.arDetector){
|
|
||||||
this.arDetector.stop();
|
|
||||||
}
|
|
||||||
if(this.player){
|
|
||||||
this.player.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resume(){
|
|
||||||
if(this.destroyed || this.invalid) {
|
|
||||||
// throw {error: 'VIDEO_DATA_DESTROYED', data: {videoData: this}};
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.paused = false;
|
|
||||||
try {
|
|
||||||
this.resizer.start();
|
|
||||||
if (this.player) {
|
|
||||||
this.player.start();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
this.logger.log('error', 'debug', "[VideoData.js::resume] cannot resume for reasons. Will destroy videoData. Error here:", e);
|
|
||||||
this.destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resumeAutoAr(){
|
|
||||||
if(this.arDetector){
|
|
||||||
this.startArDetection();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setManualTick(manualTick) {
|
|
||||||
if(this.arDetector){
|
|
||||||
this.arDetector.setManualTick(manualTick);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tick() {
|
|
||||||
if(this.arDetector){
|
|
||||||
this.arDetector.tick();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setLastAr(lastAr){
|
|
||||||
if (this.invalid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.resizer.setLastAr(lastAr);
|
|
||||||
}
|
|
||||||
|
|
||||||
setAr(ar, lastAr){
|
|
||||||
if (this.invalid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.resizer.setAr(ar, lastAr);
|
|
||||||
}
|
|
||||||
|
|
||||||
resetAr() {
|
|
||||||
if (this.invalid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.resizer.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
resetLastAr() {
|
|
||||||
if (this.invalid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.resizer.setLastAr('original');
|
|
||||||
}
|
|
||||||
|
|
||||||
panHandler(event, forcePan) {
|
|
||||||
if (this.invalid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(this.destroyed) {
|
|
||||||
// throw {error: 'VIDEO_DATA_DESTROYED', data: {videoData: this}};
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(!this.resizer) {
|
|
||||||
this.destroy();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.resizer.panHandler(event, forcePan);
|
|
||||||
}
|
|
||||||
|
|
||||||
setPanMode(mode) {
|
|
||||||
if (this.invalid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.resizer.setPanMode(mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
setVideoAlignment(videoAlignment) {
|
|
||||||
if (this.invalid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.resizer.setVideoAlignment(videoAlignment);
|
|
||||||
}
|
|
||||||
|
|
||||||
restoreAr(){
|
|
||||||
if (this.invalid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.resizer.restore();
|
|
||||||
}
|
|
||||||
|
|
||||||
setStretchMode(stretchMode){
|
|
||||||
if (this.invalid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.resizer.setStretchMode(stretchMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
setZoom(zoomLevel, no_announce){
|
|
||||||
if (this.invalid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.resizer.setZoom(zoomLevel, no_announce);
|
|
||||||
}
|
|
||||||
|
|
||||||
zoomStep(step){
|
|
||||||
if (this.invalid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.resizer.zoomStep(step);
|
|
||||||
}
|
|
||||||
|
|
||||||
announceZoom(scale){
|
|
||||||
if (this.invalid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.pageInfo.announceZoom(scale);
|
|
||||||
}
|
|
||||||
|
|
||||||
markPlayer(name, color) {
|
|
||||||
if (this.invalid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.player) {
|
|
||||||
this.player.markPlayer(name, color)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
unmarkPlayer() {
|
|
||||||
this.player.unmarkPlayer();
|
|
||||||
}
|
|
||||||
|
|
||||||
isPlaying() {
|
|
||||||
return this.video && this.video.currentTime > 0 && !this.video.paused && !this.video.ended;
|
|
||||||
}
|
|
||||||
|
|
||||||
checkVideoSizeChange(){
|
|
||||||
const videoWidth = this.video.offsetWidth;
|
|
||||||
const videoHeight = this.video.offsetHeight;
|
|
||||||
// this 'if' is just here for debugging — real code starts later. It's safe to collapse and
|
|
||||||
// ignore the contents of this if (unless we need to change how logging works)
|
|
||||||
if (this.logger.canLog('debug')){
|
|
||||||
if(! this.video) {
|
|
||||||
this.logger.log('info', 'videoDetect', "[VideoDetect] player element isn't defined");
|
|
||||||
}
|
|
||||||
if ( this.video && this.dimensions &&
|
|
||||||
( this.dimensions.width != videoWidth ||
|
|
||||||
this.dimensions.height != videoHeight )
|
|
||||||
) {
|
|
||||||
this.logger.log('info', 'debug', "[VideoDetect] player size changed. reason: dimension change. Old dimensions?", this.dimensions.width, this.dimensions.height, "new dimensions:", this.video.offsetWidth, this.video.offsetHeight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if size doesn't match, update & return true
|
|
||||||
if (!this.dimensions
|
|
||||||
|| this.dimensions.width != videoWidth
|
|
||||||
|| this.dimensions.height != videoHeight ){
|
|
||||||
this.dimensions = {
|
|
||||||
width: videoWidth,
|
|
||||||
height: videoHeight,
|
|
||||||
};
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default VideoData;
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
var RescanReason = Object.freeze({
|
|
||||||
PERIODIC: 0,
|
|
||||||
URL_CHANGE: 1,
|
|
||||||
MANUAL: 2
|
|
||||||
});
|
|
||||||
|
|
||||||
export default RescanReason;
|
|
||||||
@ -1,530 +0,0 @@
|
|||||||
import Debug from '../../conf/Debug';
|
|
||||||
import Scaler from './Scaler';
|
|
||||||
import Stretcher from './Stretcher';
|
|
||||||
import Zoom from './Zoom';
|
|
||||||
import PlayerData from '../video-data/PlayerData';
|
|
||||||
import ExtensionMode from '../../../common/enums/extension-mode.enum';
|
|
||||||
import Stretch from '../../../common/enums/stretch.enum';
|
|
||||||
import VideoAlignment from '../../../common/enums/video-alignment.enum';
|
|
||||||
import AspectRatio from '../../../common/enums/aspect-ratio.enum';
|
|
||||||
|
|
||||||
if(Debug.debug) {
|
|
||||||
console.log("Loading: Resizer.js");
|
|
||||||
}
|
|
||||||
|
|
||||||
class Resizer {
|
|
||||||
|
|
||||||
constructor(videoData) {
|
|
||||||
this.resizerId = (Math.random(99)*100).toFixed(0);
|
|
||||||
this.conf = videoData;
|
|
||||||
this.logger = videoData.logger;
|
|
||||||
this.video = videoData.video;
|
|
||||||
this.settings = videoData.settings;
|
|
||||||
this.extensionMode = videoData.extensionMode;
|
|
||||||
|
|
||||||
|
|
||||||
this.scaler = new Scaler(this.conf);
|
|
||||||
this.stretcher = new Stretcher(this.conf);
|
|
||||||
this.zoom = new Zoom(this.conf);
|
|
||||||
|
|
||||||
// load up default values
|
|
||||||
this.correctedVideoDimensions = {};
|
|
||||||
this.currentCss = {};
|
|
||||||
this.currentStyleString = "";
|
|
||||||
this.currentPlayerStyleString = "";
|
|
||||||
this.currentCssValidFor = {};
|
|
||||||
|
|
||||||
this.lastAr = {type: AspectRatio.Initial};
|
|
||||||
this.videoAlignment = this.settings.getDefaultVideoAlignment(window.location.hostname); // this is initial video alignment
|
|
||||||
this.destroyed = false;
|
|
||||||
|
|
||||||
|
|
||||||
if (this.settings.active.pan) {
|
|
||||||
this.canPan = this.settings.active.miscSettings.mousePan.enabled;
|
|
||||||
} else {
|
|
||||||
this.canPan = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.userCss = '';
|
|
||||||
this.userCssClassName = videoData.userCssClassName;
|
|
||||||
}
|
|
||||||
|
|
||||||
injectCss(css) {
|
|
||||||
this.conf.pageInfo.injectCss(css);
|
|
||||||
}
|
|
||||||
|
|
||||||
ejectCss(css) {
|
|
||||||
this.conf.pageInfo.ejectCss(css);
|
|
||||||
}
|
|
||||||
|
|
||||||
replaceCss(oldCss, newCss) {
|
|
||||||
this.conf.pageInfo.replaceCss(oldCss, newCss);
|
|
||||||
}
|
|
||||||
|
|
||||||
prepareCss(css) {
|
|
||||||
return `.${this.userCssClassName} {${css}}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy(){
|
|
||||||
this.logger.log('info', ['debug', 'init'], `[Resizer::destroy] <rid:${this.resizerId}> received destroy command.`);
|
|
||||||
this.destroyed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
calculateRatioForLegacyOptions(ar){
|
|
||||||
// also present as modeToAr in Scaler.js
|
|
||||||
if (ar.type !== AspectRatio.FitWidth && ar.type !== AspectRatio.FitHeight && ar.ratio) {
|
|
||||||
return ar;
|
|
||||||
}
|
|
||||||
// Skrbi za "stare" možnosti, kot na primer "na širino zaslona", "na višino zaslona" in "ponastavi".
|
|
||||||
// Približevanje opuščeno.
|
|
||||||
// handles "legacy" options, such as 'fit to widht', 'fit to height' and AspectRatio.Reset. No zoom tho
|
|
||||||
var ratioOut;
|
|
||||||
|
|
||||||
if (!this.conf.video) {
|
|
||||||
this.logger.log('info', 'debug', "[Scaler.js::modeToAr] No video??",this.conf.video, "killing videoData");
|
|
||||||
this.conf.destroy();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (! this.conf.player.dimensions) {
|
|
||||||
ratioOut = screen.width / screen.height;
|
|
||||||
} else {
|
|
||||||
this.logger.log('info', 'debug', `[Resizer::calculateRatioForLegacyOptions] <rid:${this.resizerId}> Player dimensions:`, this.conf.player.dimensions.width ,'x', this.conf.player.dimensions.height,'aspect ratio:', this.conf.player.dimensions.width / this.conf.player.dimensions.height)
|
|
||||||
ratioOut = this.conf.player.dimensions.width / this.conf.player.dimensions.height;
|
|
||||||
}
|
|
||||||
|
|
||||||
// POMEMBNO: lastAr je potrebno nastaviti šele po tem, ko kličemo _res_setAr(). _res_setAr() predvideva,
|
|
||||||
// da želimo nastaviti statično (type: 'static') razmerje stranic — tudi, če funkcijo kličemo tu oz. v ArDetect.
|
|
||||||
//
|
|
||||||
// IMPORTANT NOTE: lastAr needs to be set after _res_setAr() is called, as _res_setAr() assumes we're
|
|
||||||
// setting a static aspect ratio (even if the function is called from here or ArDetect).
|
|
||||||
|
|
||||||
var fileAr = this.conf.video.videoWidth / this.conf.video.videoHeight;
|
|
||||||
|
|
||||||
if (ar.type === AspectRatio.FitWidth){
|
|
||||||
ar.ratio = ratioOut > fileAr ? ratioOut : fileAr;
|
|
||||||
}
|
|
||||||
else if(ar.type === AspectRatio.FitHeight){
|
|
||||||
ar.ratio = ratioOut < fileAr ? ratioOut : fileAr;
|
|
||||||
}
|
|
||||||
else if(ar.type === AspectRatio.Reset){
|
|
||||||
this.logger.log('info', 'debug', "[Scaler.js::modeToAr] Using original aspect ratio -", fileAr);
|
|
||||||
ar.ratio = fileAr;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ar;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
setAr(ar, lastAr){
|
|
||||||
if (this.destroyed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.log('info', 'debug', '[Resizer::setAr] <rid:'+this.resizerId+'> trying to set ar. New ar:', ar)
|
|
||||||
|
|
||||||
if (ar == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ar.type === AspectRatio.Automatic ||
|
|
||||||
ar.type === AspectRatio.Reset && this.lastAr.type === AspectRatio.Initial) {
|
|
||||||
// some sites do things that interfere with our site (and aspect ratio setting in general)
|
|
||||||
// first, we check whether video contains anything we don't like
|
|
||||||
|
|
||||||
const siteSettings = this.settings.active.sites[window.location.host];
|
|
||||||
if (siteSettings && siteSettings.autoarPreventConditions) {
|
|
||||||
if (siteSettings.autoarPreventConditions.videoStyleString) {
|
|
||||||
const styleString = (this.video.getAttribute('style') || '').split(';');
|
|
||||||
|
|
||||||
if (siteSettings.autoarPreventConditions.videoStyleString.containsProperty) {
|
|
||||||
const bannedProperties = siteSettings.autoarPreventConditions.videoStyleString.containsProperty;
|
|
||||||
for (const prop in bannedProperties) {
|
|
||||||
for (const s of styleString) {
|
|
||||||
if (s.trim().startsWith(prop)) {
|
|
||||||
|
|
||||||
// check if css property has a list of allowed values:
|
|
||||||
if (bannedProperties[prop].allowedValues) {
|
|
||||||
const styleValue = s.split(':')[1].trim();
|
|
||||||
|
|
||||||
// check if property value is on the list of allowed values
|
|
||||||
// if it's not, we aren't allowed to start aard
|
|
||||||
if (bannedProperties[prop].allowedValues.indexOf(styleValue) === -1) {
|
|
||||||
this.logger.log('error', 'debug', "%c[Resizer::setAr] video style contains forbidden css property/value combo: ", "color: #900, background: #100", prop, " — we aren't allowed to start autoar.")
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// no allowed values, no problem. We have forbidden property
|
|
||||||
// and this means aard can't start.
|
|
||||||
this.logger.log('info', 'debug', "%c[Resizer::setAr] video style contains forbidden css property: ", "color: #900, background: #100", prop, " — we aren't allowed to start autoar.")
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastAr) {
|
|
||||||
this.lastAr = this.calculateRatioForLegacyOptions(lastAr);
|
|
||||||
ar = this.calculateRatioForLegacyOptions(ar);
|
|
||||||
} else {
|
|
||||||
// NOTE: "fitw" "fith" and "reset" should ignore ar.ratio bit, but
|
|
||||||
// I'm not sure whether they do. Check that.
|
|
||||||
ar = this.calculateRatioForLegacyOptions(ar);
|
|
||||||
if (! ar) {
|
|
||||||
this.logger.log('info', 'resizer', `[Resizer::setAr] <${this.resizerId}> Something wrong with ar or the player. Doing nothing.`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.lastAr = {type: ar.type, ratio: ar.ratio}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.extensionMode === ExtensionMode.Basic && !PlayerData.isFullScreen() && ar.type !== AspectRatio.Reset) {
|
|
||||||
// don't actually apply or calculate css when using basic mode if not in fullscreen
|
|
||||||
// ... unless we're resetting the aspect ratio to original
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! this.video) {
|
|
||||||
this.conf.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
// pause AR on basic stretch, unpause when using other modes
|
|
||||||
// for sine reason unpause doesn't unpause. investigate that later
|
|
||||||
try {
|
|
||||||
if (this.stretcher.mode === Stretch.Basic) {
|
|
||||||
this.conf.arDetector.pause();
|
|
||||||
} else {
|
|
||||||
if (this.lastAr.type === AspectRatio.Automatic) {
|
|
||||||
this.conf.arDetector.unpause();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) { // resizer starts before arDetector. this will do nothing but fail if arDetector isn't setup
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// do stretch thingy
|
|
||||||
if (this.stretcher.mode === Stretch.NoStretch || this.stretcher.mode === Stretch.Conditional){
|
|
||||||
var stretchFactors = this.scaler.calculateCrop(ar);
|
|
||||||
|
|
||||||
if(! stretchFactors || stretchFactors.error){
|
|
||||||
this.logger.log('error', 'debug', `[Resizer::setAr] <rid:${this.resizerId}> failed to set AR due to problem with calculating crop. Error:`, (stretchFactors ? stretchFactors.error : stretchFactors));
|
|
||||||
if (stretchFactors.error === 'no_video'){
|
|
||||||
this.conf.destroy();
|
|
||||||
}
|
|
||||||
if (stretchFactors.error === 'illegal_video_dimensions') {
|
|
||||||
this.logger.log('error', 'debug', `[Resizer::setAr] <rid:${this.resizerId}> Illegal video dimensions found. We will pause everything.`)
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(this.stretcher.mode === Stretch.Conditional){
|
|
||||||
this.stretcher.applyConditionalStretch(stretchFactors, ar.ratio);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.log('info', 'debug', "[Resizer::setAr] Processed stretch factors for ", this.stretcher.mode === Stretch.NoStretch ? 'stretch-free crop.' : 'crop with conditional stretch.', 'Stretch factors are:', stretchFactors);
|
|
||||||
|
|
||||||
} else if (this.stretcher.mode === Stretch.Hybrid) {
|
|
||||||
var stretchFactors = this.stretcher.calculateStretch(ar.ratio);
|
|
||||||
this.logger.log('info', 'debug', '[Resizer::setAr] Processed stretch factors for hybrid stretch/crop. Stretch factors are:', stretchFactors);
|
|
||||||
} else if (this.stretcher.mode === Stretch.Basic) {
|
|
||||||
var stretchFactors = this.stretcher.calculateBasicStretch();
|
|
||||||
this.logger.log('info', 'debug', '[Resizer::setAr] Processed stretch factors for basic stretch. Stretch factors are:', stretchFactors);
|
|
||||||
} else {
|
|
||||||
var stretchFactors = {xFactor: 1, yFactor: 1}
|
|
||||||
this.logger.log('error', 'debug', '[Resizer::setAr] Okay wtf happened? If you see this, something has gone wrong', stretchFactors,"\n------[ i n f o d u m p ]------\nstretcher:", this.stretcher);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.zoom.applyZoom(stretchFactors);
|
|
||||||
|
|
||||||
//TODO: correct these two
|
|
||||||
var translate = this.computeOffsets(stretchFactors);
|
|
||||||
this.applyCss(stretchFactors, translate);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
resetLastAr() {
|
|
||||||
this.lastAr = {type: AspectRatio.Initial};
|
|
||||||
}
|
|
||||||
|
|
||||||
setLastAr(override){
|
|
||||||
this.lastAr = override;
|
|
||||||
}
|
|
||||||
|
|
||||||
getLastAr(){
|
|
||||||
return this.lastAr;
|
|
||||||
}
|
|
||||||
|
|
||||||
setStretchMode(stretchMode){
|
|
||||||
this.stretcher.setStretchMode(stretchMode);
|
|
||||||
this.restore();
|
|
||||||
}
|
|
||||||
|
|
||||||
panHandler(event, forcePan) {
|
|
||||||
if (this.canPan || forcePan) {
|
|
||||||
if(!this.conf.player || !this.conf.player.element) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// dont allow weird floats
|
|
||||||
this.videoAlignment = VideoAlignment.Center;
|
|
||||||
|
|
||||||
const player = this.conf.player.element;
|
|
||||||
|
|
||||||
const relativeX = (event.pageX - player.offsetLeft) / player.offsetWidth;
|
|
||||||
const relativeY = (event.pageY - player.offsetTop) / player.offsetHeight;
|
|
||||||
|
|
||||||
this.logger.log('info', 'mousemove', "[Resizer::panHandler] mousemove.pageX, pageY:", event.pageX, event.pageY, "\nrelativeX/Y:", relativeX, relativeY)
|
|
||||||
|
|
||||||
this.setPan(relativeX, relativeY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setPan(relativeMousePosX, relativeMousePosY){
|
|
||||||
// relativeMousePos[X|Y] - on scale from 0 to 1, how close is the mouse to player edges.
|
|
||||||
// use these values: top, left: 0, bottom, right: 1
|
|
||||||
if(! this.pan){
|
|
||||||
this.pan = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.settings.active.miscSettings.mousePanReverseMouse) {
|
|
||||||
this.pan.relativeOffsetX = (relativeMousePosX * 1.1) - 0.55;
|
|
||||||
this.pan.relativeOffsetY = (relativeMousePosY * 1.1) - 0.55;
|
|
||||||
} else {
|
|
||||||
this.pan.relativeOffsetX = -(relativeMousePosX * 1.1) + 0.55;
|
|
||||||
this.pan.relativeOffsetY = -(relativeMousePosY * 1.1) + 0.55;
|
|
||||||
}
|
|
||||||
this.restore();
|
|
||||||
}
|
|
||||||
|
|
||||||
setVideoAlignment(videoAlignment) {
|
|
||||||
this.videoAlignment = videoAlignment;
|
|
||||||
this.restore();
|
|
||||||
}
|
|
||||||
|
|
||||||
restore() {
|
|
||||||
this.logger.log('info', 'debug', "[Resizer::restore] <rid:"+this.resizerId+"> attempting to restore aspect ratio. this & settings:", {'a_lastAr': this.lastAr, 'this': this, "settings": this.settings} );
|
|
||||||
|
|
||||||
// this is true until we verify that css has actually been applied
|
|
||||||
if(this.lastAr.type === AspectRatio.Initial){
|
|
||||||
this.setAr({type: AspectRatio.Reset});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (this.lastAr && this.lastAr.ratio === null) {
|
|
||||||
// if this is the case, we do nothing as we have the correct aspect ratio
|
|
||||||
// throw "Last ar is null!"
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.setAr(this.lastAr, this.lastAr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
reset(){
|
|
||||||
this.setStretchMode(this.settings.active.sites[window.location.hostname] ? this.settings.active.sites[window.location.hostname].stretch : this.settings.active.sites['@global'].stretch);
|
|
||||||
this.zoom.setZoom(1);
|
|
||||||
this.resetPan();
|
|
||||||
this.setAr({type: AspectRatio.Reset});
|
|
||||||
}
|
|
||||||
|
|
||||||
setPanMode(mode) {
|
|
||||||
if (mode === 'enable') {
|
|
||||||
this.canPan = true;
|
|
||||||
} else if (mode === 'disable') {
|
|
||||||
this.canPan = false;
|
|
||||||
} else if (mode === 'toggle') {
|
|
||||||
this.canPan = !this.canPan;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resetPan(){
|
|
||||||
this.pan = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
setZoom(zoomLevel, no_announce) {
|
|
||||||
this.zoom.setZoom(zoomLevel, no_announce);
|
|
||||||
}
|
|
||||||
|
|
||||||
zoomStep(step){
|
|
||||||
this.zoom.zoomStep(step);
|
|
||||||
}
|
|
||||||
|
|
||||||
resetZoom(){
|
|
||||||
this.zoom.setZoom(1);
|
|
||||||
this.restore();
|
|
||||||
}
|
|
||||||
|
|
||||||
resetCrop(){
|
|
||||||
this.setAr({type: AspectRatio.Reset});
|
|
||||||
}
|
|
||||||
|
|
||||||
resetStretch(){
|
|
||||||
this.stretcher.setStretchMode(Stretch.NoStretch);
|
|
||||||
this.restore();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// mostly internal stuff
|
|
||||||
|
|
||||||
computeOffsets(stretchFactors){
|
|
||||||
|
|
||||||
this.logger.log('info', 'debug', "[Resizer::computeOffsets] <rid:"+this.resizerId+"> video will be aligned to ", this.settings.active.sites['@global'].videoAlignment);
|
|
||||||
|
|
||||||
const wdiff = this.conf.player.dimensions.width - this.conf.video.offsetWidth;
|
|
||||||
const hdiff = this.conf.player.dimensions.height - this.conf.video.offsetHeight;
|
|
||||||
|
|
||||||
const wdiffAfterZoom = this.conf.video.offsetWidth * stretchFactors.xFactor - this.conf.player.dimensions.width;
|
|
||||||
const hdiffAfterZoom = this.conf.video.offsetHeight * stretchFactors.yFactor - this.conf.player.dimensions.height;
|
|
||||||
|
|
||||||
var translate = {
|
|
||||||
x: wdiff * 0.5,
|
|
||||||
y: hdiff * 0.5,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.pan) {
|
|
||||||
// don't offset when video is smaller than player
|
|
||||||
if(wdiffAfterZoom < 0 && hdiffAfterZoom < 0) {
|
|
||||||
return translate;
|
|
||||||
}
|
|
||||||
translate.x += wdiffAfterZoom * this.pan.relativeOffsetX * this.zoom.scale;
|
|
||||||
translate.y += hdiffAfterZoom * this.pan.relativeOffsetY * this.zoom.scale;
|
|
||||||
} else {
|
|
||||||
if (this.videoAlignment == VideoAlignment.Left) {
|
|
||||||
translate.x += wdiffAfterZoom * 0.5;
|
|
||||||
}
|
|
||||||
else if (this.videoAlignment == VideoAlignment.Right) {
|
|
||||||
translate.x -= wdiffAfterZoom * 0.5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.log('info', 'debug', "[Resizer::_res_computeOffsets] <rid:"+this.resizerId+"> calculated offsets:\n\n",
|
|
||||||
'---- data in ----\n',
|
|
||||||
'player dimensions:', {w: this.conf.player.dimensions.width, h: this.conf.player.dimensions.height},
|
|
||||||
'video dimensions: ', {w: this.conf.video.offsetWidth, h: this.conf.video.offsetHeight},
|
|
||||||
'stretch factors: ', stretchFactors,
|
|
||||||
'pan & zoom: ', this.pan, this.zoom,
|
|
||||||
'\n\n---- data out ----\n',
|
|
||||||
'translate:', translate);
|
|
||||||
|
|
||||||
return translate;
|
|
||||||
}
|
|
||||||
|
|
||||||
buildStyleArray(existingStyleString, extraStyleString) {
|
|
||||||
if (existingStyleString) {
|
|
||||||
const styleArray = existingStyleString.split(";");
|
|
||||||
|
|
||||||
if (extraStyleString) {
|
|
||||||
const extraCss = extraStyleString.split(';');
|
|
||||||
let dup = false;
|
|
||||||
|
|
||||||
for (const ecss of extraCss) {
|
|
||||||
for (let i in styleArray) {
|
|
||||||
if (ecss.split(':')[0].trim() === styleArray[i].split(':')[0].trim()) {
|
|
||||||
dup = true;
|
|
||||||
styleArray[i] = ecss;
|
|
||||||
}
|
|
||||||
if (dup) {
|
|
||||||
dup = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
styleArray.push(ecss);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i in styleArray) {
|
|
||||||
styleArray[i] = styleArray[i].trim();
|
|
||||||
// some sites do 'top: 50%; left: 50%; transform: <transform>' to center videos.
|
|
||||||
// we dont wanna, because we already center videos on our own
|
|
||||||
if (styleArray[i].startsWith("transform:") ||
|
|
||||||
styleArray[i].startsWith("top:") ||
|
|
||||||
styleArray[i].startsWith("left:") ||
|
|
||||||
styleArray[i].startsWith("right:") ||
|
|
||||||
styleArray[i].startsWith("bottom:") ){
|
|
||||||
delete styleArray[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return styleArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
buildStyleString(styleArray) {
|
|
||||||
let styleString = '';
|
|
||||||
|
|
||||||
for(var i in styleArray) {
|
|
||||||
if(styleArray[i]) {
|
|
||||||
styleString += styleArray[i] + " !important; ";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return styleString;
|
|
||||||
}
|
|
||||||
|
|
||||||
applyCss(stretchFactors, translate){
|
|
||||||
// apply extra CSS here. In case of duplicated properties, extraCss overrides
|
|
||||||
// default styleString
|
|
||||||
if (! this.video) {
|
|
||||||
this.logger.log('warn', 'debug', "[Resizer::applyCss] <rid:"+this.resizerId+"> Video went missing, doing nothing.");
|
|
||||||
|
|
||||||
this.conf.destroy();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.log('info', 'resizer', "[Resizer::applyCss] <rid:"+this.resizerId+"> will apply css.", {stretchFactors, translate});
|
|
||||||
|
|
||||||
// save stuff for quick tests (before we turn numbers into css values):
|
|
||||||
this.currentVideoSettings = {
|
|
||||||
validFor: this.conf.player.dimensions,
|
|
||||||
// videoWidth: dimensions.width,
|
|
||||||
// videoHeight: dimensions.height
|
|
||||||
}
|
|
||||||
|
|
||||||
let extraStyleString;
|
|
||||||
try {
|
|
||||||
extraStyleString = this.settings.active.sites[window.location.host].DOM.video.additionalCss;
|
|
||||||
} catch (e) {
|
|
||||||
// do nothing. It's ok if no special settings are defined for this site, we'll just do defaults
|
|
||||||
}
|
|
||||||
|
|
||||||
const styleArray = this.buildStyleArray('', extraStyleString)
|
|
||||||
|
|
||||||
// add remaining elements
|
|
||||||
if (stretchFactors) {
|
|
||||||
styleArray.push(`transform: translate(${translate.x}px, ${translate.y}px) scale(${stretchFactors.xFactor}, ${stretchFactors.yFactor})`);
|
|
||||||
|
|
||||||
// important — guarantees video will be properly aligned
|
|
||||||
styleArray.push("top: 0px !important; left: 0px !important; bottom: 0px !important; right: 0px;");
|
|
||||||
|
|
||||||
// important — some websites (cough reddit redesign cough) may impose some dumb max-width and max-height
|
|
||||||
// restrictions. If site has dumb shit like 'max-width: 100%' and 'max-height: 100vh' in their CSS, that
|
|
||||||
// shit will prevent us from applying desired crop. This means we need to tell websites to fuck off with
|
|
||||||
// that crap. We know better.
|
|
||||||
styleArray.push("max-width: none !important; max-height: none !important;");
|
|
||||||
}
|
|
||||||
const styleString = `${this.buildStyleString(styleArray)}${extraStyleString || ''}`; // string returned by buildStyleString() should end with ; anyway
|
|
||||||
|
|
||||||
// build style string back
|
|
||||||
this.setStyleString(styleString);
|
|
||||||
}
|
|
||||||
|
|
||||||
setStyleString (styleString) {
|
|
||||||
this.currentCssValidFor = this.conf.player.dimensions;
|
|
||||||
const newCssString = this.prepareCss(styleString);
|
|
||||||
|
|
||||||
// inject new CSS or replace existing one
|
|
||||||
if (!this.userCss) {
|
|
||||||
this.injectCss(newCssString);
|
|
||||||
this.userCss = newCssString;
|
|
||||||
} else if (newCssString !== this.userCss) {
|
|
||||||
// we only replace css if it
|
|
||||||
this.replaceCss(this.userCss, newCssString);
|
|
||||||
this.userCss = newCssString;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Resizer;
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user