diff --git a/.prettierignore b/.prettierignore
index 9481208..4bcade1 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -4,3 +4,6 @@ pnpm-lock.yaml
.husky
.prettierignore
.gitignore
+*.ttf
+*.otf
+*.woff2
diff --git a/package.json b/package.json
index 142c93a..fc7bf2b 100644
--- a/package.json
+++ b/package.json
@@ -35,6 +35,7 @@
"license": "MIT",
"devDependencies": {
"@crowdin/cli": "^3.7.10",
+ "@hrgui/libass-wasm-ts": "^1.0.3",
"@types/mark.js": "^8.11.8",
"@types/node": "^18.7.5",
"@types/sha256": "^0.2.0",
@@ -43,6 +44,7 @@
"husky": "^8.0.2",
"lint-staged": "^13.0.4",
"prettier": "3.0.0",
+ "rollup-plugin-copy": "^3.5.0",
"terser": "^5.14.2",
"typescript": "^4.7.4",
"vite": "^3.0.8",
@@ -67,6 +69,7 @@
"flv.js": "^1.6.2",
"hls.js": "^1.2.1",
"just-once": "^2.2.0",
+ "libass-wasm": "^4.1.0",
"lightgallery": "^2.5.0",
"mark.js": "^8.11.1",
"mitt": "^3.0.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 1bf5bc1..b7f1dcc 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -56,6 +56,9 @@ dependencies:
just-once:
specifier: ^2.2.0
version: 2.2.0
+ libass-wasm:
+ specifier: ^4.1.0
+ version: 4.1.0
lightgallery:
specifier: ^2.5.0
version: 2.5.0
@@ -109,6 +112,9 @@ devDependencies:
'@crowdin/cli':
specifier: ^3.7.10
version: 3.7.10
+ '@hrgui/libass-wasm-ts':
+ specifier: ^1.0.3
+ version: 1.0.3
'@types/mark.js':
specifier: ^8.11.8
version: 8.11.8
@@ -133,6 +139,9 @@ devDependencies:
prettier:
specifier: 3.0.0
version: 3.0.0
+ rollup-plugin-copy:
+ specifier: ^3.5.0
+ version: 3.5.0
terser:
specifier: ^5.14.2
version: 5.14.2
@@ -520,6 +529,10 @@ packages:
solid-transition-group: 0.0.12(solid-js@1.4.8)
dev: false
+ /@hrgui/libass-wasm-ts@1.0.3:
+ resolution: {integrity: sha512-n8RbJLrhirfgDun88jVSs0/SeLC5PZz9iost9DXZ9dAXztDzpmjlEfu+k/viM37+EbaC9gWnRdUwcnptDkjtNw==}
+ dev: true
+
/@jridgewell/gen-mapping@0.1.1:
resolution: {integrity: sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==}
engines: {node: '>=6.0.0'}
@@ -633,6 +646,27 @@ packages:
tslib: 2.4.0
dev: false
+ /@nodelib/fs.scandir@2.1.5:
+ resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
+ engines: {node: '>= 8'}
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ run-parallel: 1.2.0
+ dev: true
+
+ /@nodelib/fs.stat@2.0.5:
+ resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
+ engines: {node: '>= 8'}
+ dev: true
+
+ /@nodelib/fs.walk@1.2.8:
+ resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
+ engines: {node: '>= 8'}
+ dependencies:
+ '@nodelib/fs.scandir': 2.1.5
+ fastq: 1.15.0
+ dev: true
+
/@solid-primitives/event-listener@2.3.0(solid-js@1.4.8):
resolution: {integrity: sha512-0DS7DQZvCExWSpurVZC9/wjI8RmkhuOtWOy6Pp1Woq9ElMT9/bfjNpkwXsOwisLpcTqh9eUs17kp7jtpWcC20w==}
peerDependencies:
@@ -713,6 +747,19 @@ packages:
'@types/ms': 0.7.31
dev: false
+ /@types/fs-extra@8.1.4:
+ resolution: {integrity: sha512-OMcQKnlrkrOI0TaZ/MgVDA8LYFl7CykzFsjMj9l5x3un2nFxCY20ZFlnqrM0lcqlbs0Yro2HbnZlmopyRaoJ5w==}
+ dependencies:
+ '@types/node': 18.7.5
+ dev: true
+
+ /@types/glob@7.2.0:
+ resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==}
+ dependencies:
+ '@types/minimatch': 5.1.2
+ '@types/node': 18.7.5
+ dev: true
+
/@types/hast@2.3.4:
resolution: {integrity: sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==}
dependencies:
@@ -749,6 +796,10 @@ packages:
resolution: {integrity: sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==}
dev: false
+ /@types/minimatch@5.1.2:
+ resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==}
+ dev: true
+
/@types/ms@0.7.31:
resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==}
dev: false
@@ -853,6 +904,11 @@ packages:
smoothscroll: 0.4.0
dev: false
+ /array-union@2.1.0:
+ resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
+ engines: {node: '>=8'}
+ dev: true
+
/artplayer-plugin-danmuku@4.4.11:
resolution: {integrity: sha512-/4F8IyB29Bdr1LV1RM3FM7CdiAMsVNOOJ/HY4jaJCXnMAKThQiGa++0PhCJ07nhyGvDcZGRL2/UWJRz9zZJFNw==}
dev: false
@@ -1036,6 +1092,10 @@ packages:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==, tarball: https://registry.npm.taobao.org/color-name/-/color-name-1.1.4.tgz}
dev: true
+ /colorette@1.4.0:
+ resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==}
+ dev: true
+
/colorette@2.0.19:
resolution: {integrity: sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==, tarball: https://registry.npm.taobao.org/colorette/-/colorette-2.0.19.tgz}
dev: true
@@ -1159,6 +1219,13 @@ packages:
engines: {node: '>=0.3.1'}
dev: false
+ /dir-glob@3.0.1:
+ resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
+ engines: {node: '>=8'}
+ dependencies:
+ path-type: 4.0.0
+ dev: true
+
/dom-serializer@1.4.1:
resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==}
dependencies:
@@ -1458,6 +1525,23 @@ packages:
resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
dev: false
+ /fast-glob@3.3.1:
+ resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==}
+ engines: {node: '>=8.6.0'}
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ '@nodelib/fs.walk': 1.2.8
+ glob-parent: 5.1.2
+ merge2: 1.4.1
+ micromatch: 4.0.5
+ dev: true
+
+ /fastq@1.15.0:
+ resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==}
+ dependencies:
+ reusify: 1.0.4
+ dev: true
+
/fd-slicer@1.1.0:
resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==}
dependencies:
@@ -1503,6 +1587,15 @@ packages:
mime-types: 2.1.35
dev: false
+ /fs-extra@8.1.0:
+ resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==}
+ engines: {node: '>=6 <7 || >=8'}
+ dependencies:
+ graceful-fs: 4.2.11
+ jsonfile: 4.0.0
+ universalify: 0.1.2
+ dev: true
+
/fs-minipass@1.2.7:
resolution: {integrity: sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==}
dependencies:
@@ -1535,6 +1628,13 @@ packages:
engines: {node: '>=10'}
dev: true
+ /glob-parent@5.1.2:
+ resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
+ engines: {node: '>= 6'}
+ dependencies:
+ is-glob: 4.0.3
+ dev: true
+
/glob@7.2.3:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
dependencies:
@@ -1551,6 +1651,24 @@ packages:
engines: {node: '>=4'}
dev: true
+ /globby@10.0.1:
+ resolution: {integrity: sha512-sSs4inE1FB2YQiymcmTv6NWENryABjUNPeWhOvmn4SjtKybglsyPZxFB3U1/+L1bYi0rNZDqCLlHyLYDl1Pq5A==}
+ engines: {node: '>=8'}
+ dependencies:
+ '@types/glob': 7.2.0
+ array-union: 2.1.0
+ dir-glob: 3.0.1
+ fast-glob: 3.3.1
+ glob: 7.2.3
+ ignore: 5.2.4
+ merge2: 1.4.1
+ slash: 3.0.0
+ dev: true
+
+ /graceful-fs@4.2.11:
+ resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
+ dev: true
+
/has-flag@3.0.0:
resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
engines: {node: '>=4'}
@@ -1705,6 +1823,11 @@ packages:
hasBin: true
dev: true
+ /ignore@5.2.4:
+ resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==}
+ engines: {node: '>= 4'}
+ dev: true
+
/indent-string@4.0.0:
resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==, tarball: https://registry.npm.taobao.org/indent-string/-/indent-string-4.0.0.tgz}
engines: {node: '>=8'}
@@ -1741,6 +1864,11 @@ packages:
has: 1.0.3
dev: true
+ /is-extglob@2.1.1:
+ resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
/is-fullwidth-code-point@3.0.0:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==, tarball: https://registry.npm.taobao.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz}
engines: {node: '>=8'}
@@ -1751,6 +1879,13 @@ packages:
engines: {node: '>=12'}
dev: true
+ /is-glob@4.0.3:
+ resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ is-extglob: 2.1.1
+ dev: true
+
/is-number@7.0.0:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==, tarball: https://registry.npm.taobao.org/is-number/-/is-number-7.0.0.tgz}
engines: {node: '>=0.12.0'}
@@ -1761,6 +1896,11 @@ packages:
engines: {node: '>=12'}
dev: false
+ /is-plain-object@3.0.1:
+ resolution: {integrity: sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
/is-stream@3.0.0:
resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==, tarball: https://registry.npm.taobao.org/is-stream/-/is-stream-3.0.0.tgz}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
@@ -1791,6 +1931,12 @@ packages:
hasBin: true
dev: true
+ /jsonfile@4.0.0:
+ resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==}
+ optionalDependencies:
+ graceful-fs: 4.2.11
+ dev: true
+
/just-once@2.2.0:
resolution: {integrity: sha512-Wo547FgUOUZ98jbrZ1KX8nRezdEdtgIlC2NK1u1RvR1oZ/WoU++FjprP8J8hRbaox776MHyeMZZED4DvhhHVjg==}
dev: false
@@ -1812,6 +1958,10 @@ packages:
engines: {node: '>=6'}
dev: false
+ /libass-wasm@4.1.0:
+ resolution: {integrity: sha512-+RbYT/uuI6VHExCmGyUuMg3A2gQOaCRTzSn8GGDSf3q4cEoUNiINd9u4RGfZXA1UKafW+Hv8bmcKIX4FKbSh0Q==}
+ dev: false
+
/lightgallery@2.5.0:
resolution: {integrity: sha512-pzg5gwflLGlKaK1VqDKpb7yqQ/WSV/TxFaHRR7ld7G5iOI9gkJZ2qKKo2lyqvTvu8yAqNYXoU0w9O/Dll6POkw==}
engines: {node: '>=6.0.0'}
@@ -2043,6 +2193,11 @@ packages:
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==, tarball: https://registry.npm.taobao.org/merge-stream/-/merge-stream-2.0.0.tgz}
dev: true
+ /merge2@1.4.1:
+ resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
+ engines: {node: '>= 8'}
+ dev: true
+
/micromark-core-commonmark@1.0.6:
resolution: {integrity: sha512-K+PkJTxqjFfSNkfAhp4GB+cZPfQd6dxtTXnf+RjZOV7T4EEXnvgzOcnp+eSTmpGk9d1S9sL6/lqrgSNn/s0HZA==}
dependencies:
@@ -2505,6 +2660,11 @@ packages:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
dev: true
+ /path-type@4.0.0:
+ resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
+ engines: {node: '>=8'}
+ dev: true
+
/pend@1.2.0:
resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==}
dev: true
@@ -2551,6 +2711,10 @@ packages:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
dev: false
+ /queue-microtask@1.2.3:
+ resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
+ dev: true
+
/rechoir@0.6.2:
resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==}
engines: {node: '>= 0.10'}
@@ -2637,10 +2801,26 @@ packages:
signal-exit: 3.0.7
dev: true
+ /reusify@1.0.4:
+ resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
+ engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
+ dev: true
+
/rfdc@1.3.0:
resolution: {integrity: sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==, tarball: https://registry.npm.taobao.org/rfdc/-/rfdc-1.3.0.tgz}
dev: true
+ /rollup-plugin-copy@3.5.0:
+ resolution: {integrity: sha512-wI8D5dvYovRMx/YYKtUNt3Yxaw4ORC9xo6Gt9t22kveWz1enG9QrhVlagzwrxSC455xD1dHMKhIJkbsQ7d48BA==}
+ engines: {node: '>=8.3'}
+ dependencies:
+ '@types/fs-extra': 8.1.4
+ colorette: 1.4.0
+ fs-extra: 8.1.0
+ globby: 10.0.1
+ is-plain-object: 3.0.1
+ dev: true
+
/rollup@2.77.3:
resolution: {integrity: sha512-/qxNTG7FbmefJWoeeYJFbHehJ2HNWnjkAFRKzWN/45eNBBF/r8lo992CwcJXEzyVxs5FmfId+vTSTQDb+bxA+g==}
engines: {node: '>=10.0.0'}
@@ -2649,6 +2829,12 @@ packages:
fsevents: 2.3.2
dev: true
+ /run-parallel@1.2.0:
+ resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
+ dependencies:
+ queue-microtask: 1.2.3
+ dev: true
+
/rxjs@7.5.7:
resolution: {integrity: sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA==, tarball: https://registry.npm.taobao.org/rxjs/-/rxjs-7.5.7.tgz}
dependencies:
@@ -2716,6 +2902,11 @@ packages:
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==, tarball: https://registry.npm.taobao.org/signal-exit/-/signal-exit-3.0.7.tgz}
dev: true
+ /slash@3.0.0:
+ resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
+ engines: {node: '>=8'}
+ dev: true
+
/slice-ansi@3.0.0:
resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==, tarball: https://registry.npm.taobao.org/slice-ansi/-/slice-ansi-3.0.0.tgz}
engines: {node: '>=8'}
@@ -3055,6 +3246,11 @@ packages:
unist-util-visit-parents: 5.1.0
dev: false
+ /universalify@0.1.2:
+ resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==}
+ engines: {node: '>= 4.0.0'}
+ dev: true
+
/update-browserslist-db@1.0.5(browserslist@4.21.3):
resolution: {integrity: sha512-dteFFpCyvuDdr9S/ff1ISkKt/9YZxKjI9WlRR99c180GaztJtRa/fn18FdxGVKVsnPY7/a/FDN68mcvUmP4U7Q==}
hasBin: true
diff --git a/src/components/artplayer-plugin-ass/fonts/SourceHanSansCN-Bold.woff2 b/src/components/artplayer-plugin-ass/fonts/SourceHanSansCN-Bold.woff2
new file mode 100644
index 0000000..28d1506
Binary files /dev/null and b/src/components/artplayer-plugin-ass/fonts/SourceHanSansCN-Bold.woff2 differ
diff --git a/src/components/artplayer-plugin-ass/fonts/TimesNewRoman.ttf b/src/components/artplayer-plugin-ass/fonts/TimesNewRoman.ttf
new file mode 100644
index 0000000..eaf5e11
Binary files /dev/null and b/src/components/artplayer-plugin-ass/fonts/TimesNewRoman.ttf differ
diff --git a/src/components/artplayer-plugin-ass/index.d.ts b/src/components/artplayer-plugin-ass/index.d.ts
new file mode 100644
index 0000000..4b51618
--- /dev/null
+++ b/src/components/artplayer-plugin-ass/index.d.ts
@@ -0,0 +1,12 @@
+import type Artplayer from "artplayer"
+import type SubtitlesOctopus from "libass-wasm"
+import { type Options } from "libass-wasm"
+
+export = artplayerPluginAss
+export as namespace artplayerPluginAss
+type Ass = {
+ name: "artplayerPluginAss"
+ instance: SubtitlesOctopus
+}
+
+declare const artplayerPluginAss: (options: Options) => (art: Artplayer) => Ass
diff --git a/src/components/artplayer-plugin-ass/index.js b/src/components/artplayer-plugin-ass/index.js
new file mode 100644
index 0000000..8bbefc8
--- /dev/null
+++ b/src/components/artplayer-plugin-ass/index.js
@@ -0,0 +1,62 @@
+import SubtitlesOctopus from "libass-wasm"
+import legacyWorkerUrl from "libass-wasm/dist/js/subtitles-octopus-worker-legacy.js?url"
+import workerUrl from "libass-wasm/dist/js/subtitles-octopus-worker.js?url"
+
+import TimesNewRomanFont from "./fonts/TimesNewRoman.ttf?url"
+import fallbackFont from "./fonts/SourceHanSansCN-Bold.woff2?url"
+
+let instance = null
+
+function setVisible(visible) {
+ if (instance.canvasParent)
+ instance.canvasParent.style.display = visible ? "block" : "none"
+}
+
+function artplayerPluginAss(options) {
+ return (art) => {
+ instance = new SubtitlesOctopus({
+ // TODO: load available fonts from manage panel
+ availableFonts: {
+ "times new roman": TimesNewRomanFont,
+ },
+ fallbackFont,
+ legacyWorkerUrl,
+ workerUrl,
+ video: art.template.$video,
+ ...options,
+ })
+
+ instance.canvasParent.className = "artplayer-plugin-ass"
+ instance.canvasParent.style.cssText = `
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ user-select: none;
+ pointer-events: none;
+ z-index: 20;
+ `
+ // switch subtitle track
+ art.on("artplayer-plugin-ass:switch", (subtitle) => {
+ instance.freeTrack()
+ instance.setTrackByUrl(subtitle)
+ setVisible(true)
+ })
+
+ // set subtitle visible
+ art.on("subtitle", (visible) => setVisible(visible))
+ art.on("artplayer-plugin-ass:visible", (visible) => setVisible(visible))
+
+ // set subtitle offset
+ art.on("subtitleOffset", (offset) => (instance.timeOffset = offset))
+
+ // when player destory
+ art.on("destroy", () => instance.dispose())
+
+ return {
+ name: "artplayerPluginAss",
+ instance: instance,
+ }
+ }
+}
+
+export default artplayerPluginAss
diff --git a/src/components/icons.tsx b/src/components/icons.tsx
index b5900cb..09008ab 100644
--- a/src/components/icons.tsx
+++ b/src/components/icons.tsx
@@ -58,3 +58,16 @@ export function VscodeIconsFileTypeAi2(props: IconProps) {
props,
)
}
+
+export function ArtPlayerIconsSubtitle(props: IconProps) {
+ return IconTemplate(
+ {
+ a: {
+ viewBox: "0 0 48 48",
+ },
+ c: `
+ `,
+ },
+ props,
+ )
+}
diff --git a/src/pages/home/previews/video.tsx b/src/pages/home/previews/video.tsx
index 17b81fe..ba5907d 100644
--- a/src/pages/home/previews/video.tsx
+++ b/src/pages/home/previews/video.tsx
@@ -6,11 +6,15 @@ import { ObjType } from "~/types"
import { ext } from "~/utils"
import Artplayer from "artplayer"
import { type Option } from "artplayer/types/option"
+import { type Setting } from "artplayer/types/setting"
+import { type Events } from "artplayer/types/events"
import artplayerPluginDanmuku from "artplayer-plugin-danmuku"
+import artplayerPluginAss from "~/components/artplayer-plugin-ass"
import flvjs from "flv.js"
import Hls from "hls.js"
import { currentLang } from "~/app/i18n"
import { VideoBox } from "./video_box"
+import { ArtPlayerIconsSubtitle } from "~/components/icons"
const Preview = () => {
const { replace, pathname } = useRouter()
@@ -80,7 +84,7 @@ const Preview = () => {
},
},
lang: ["en", "zh-cn", "zh-tw"].includes(currentLang().toLowerCase())
- ? (currentLang().toLowerCase() as any)
+ ? (currentLang().toLowerCase() as string)
: "en",
lock: true,
fastForward: true,
@@ -104,51 +108,120 @@ const Preview = () => {
}
return false
})
+
+ // TODO: add a switch in manage panel to choose whether to enable `libass-wasm`
+ const enableEnhanceAss = true
+
if (subtitle.length != 0) {
- option.subtitle = {
- url: proxyLink(subtitle[0], true),
- type: ext(subtitle[0].name) as any,
+ let isEnhanceAssMode = false
+
+ // set default subtitle
+ const defaultSubtitle = subtitle[0]
+ if (enableEnhanceAss && ext(defaultSubtitle.name).toLowerCase() === "ass") {
+ isEnhanceAssMode = true
+ option.plugins?.push(
+ artplayerPluginAss({
+ // debug: true,
+ subUrl: proxyLink(defaultSubtitle, true),
+ }),
+ )
+ } else {
+ option.subtitle = {
+ url: proxyLink(defaultSubtitle, true),
+ type: ext(defaultSubtitle.name),
+ }
+ }
+
+ // render subtitle toggle menu
+ const innerMenu: Setting[] = [
+ {
+ id: "setting_subtitle_display",
+ html: "Display",
+ tooltip: "Show",
+ switch: true,
+ onSwitch: function (item: Setting) {
+ item.tooltip = item.switch ? "Hide" : "Show"
+ setSubtitleVisible(!item.switch)
+
+ // sync menu subtitle tooltip
+ const menu_sub = option.settings?.find(
+ (_) => _.id === "setting_subtitle",
+ )
+ menu_sub && (menu_sub.tooltip = item.tooltip)
+
+ return !item.switch
+ },
+ },
+ ]
+ subtitle.forEach((item, i) => {
+ innerMenu.push({
+ default: i === 0,
+ html: (
+
+ {item.name}
+
+ ) as HTMLElement,
+ name: item.name,
+ url: proxyLink(item, true),
+ })
+ })
+
+ option.settings?.push({
+ id: "setting_subtitle",
+ html: "Subtitle",
+ tooltip: "Show",
+ icon: ArtPlayerIconsSubtitle({ size: 24 }) as HTMLElement,
+ selector: innerMenu,
+ onSelect: function (item: Setting) {
+ if (enableEnhanceAss && ext(item.name).toLowerCase() === "ass") {
+ isEnhanceAssMode = true
+ this.emit("artplayer-plugin-ass:switch" as keyof Events, item.url)
+ setSubtitleVisible(true)
+ } else {
+ isEnhanceAssMode = false
+ this.subtitle.switch(item.url, { name: item.name })
+ this.once("subtitleLoad", setSubtitleVisible.bind(this, true))
+ }
+
+ const switcher = innerMenu.find(
+ (_) => _.id === "setting_subtitle_display",
+ )
+
+ if (switcher && !switcher.switch) switcher.$html?.click?.()
+
+ // sync from display switcher
+ return switcher?.tooltip
+ },
+ })
+
+ function setSubtitleVisible(visible: boolean) {
+ const type = isEnhanceAssMode ? "ass" : "webvtt"
+
+ switch (type) {
+ case "ass":
+ player.subtitle.show = false
+ player.emit("artplayer-plugin-ass:visible" as keyof Events, visible)
+ break
+
+ case "webvtt":
+ default:
+ player.subtitle.show = visible
+ player.emit("artplayer-plugin-ass:visible" as keyof Events, false)
+ break
+ }
}
}
- if (subtitle.length != 0) {
- const selector = []
- selector.push({
- html: "Display",
- tooltip: "Show",
- switch: true,
- onSwitch: function (item: Setting) {
- item.tooltip = item.switch ? "Hide" : "Show"
- this.subtitle.show = !item.switch
- return !item.switch
- },
- })
- subtitle.map((subtitleOne, i) => {
- selector.push({
- default: i == 0 ? true : false,
- html:
- subtitleOne.name.length < 30
- ? subtitleOne.name
- : subtitleOne.name.substr(-30, 30),
- url: proxyLink(subtitleOne, true),
- })
- })
- option.settings.push({
- html: "Subtitle",
- tooltip: subtitle[0].name,
- icon: '
',
- selector: selector,
- onSelect: function (item: Setting) {
- this.subtitle.switch(item.url, {
- name: item.html,
- })
- return item.html
- },
- })
- }
-
if (danmu) {
- option.plugins = [
+ option.plugins?.push(
artplayerPluginDanmuku({
danmuku: proxyLink(danmu, true),
speed: 5,
@@ -166,7 +239,7 @@ const Preview = () => {
maxWidth: 400,
theme: "dark",
}),
- ]
+ )
}
onMount(() => {
player = new Artplayer(option)
diff --git a/tsconfig.json b/tsconfig.json
index 4b1a5ae..7d8a3ce 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -8,7 +8,7 @@
"esModuleInterop": true,
"jsx": "preserve",
"jsxImportSource": "solid-js",
- "types": ["vite/client"],
+ "types": ["vite/client", "@hrgui/libass-wasm-ts"],
"noEmit": true,
"isolatedModules": false,
"paths": {
diff --git a/vite.config.ts b/vite.config.ts
index d2c17db..2b5f620 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -3,6 +3,7 @@ import { defineConfig } from "vite"
import solidPlugin from "vite-plugin-solid"
import legacy from "@vitejs/plugin-legacy"
import { dynamicBase } from "vite-plugin-dynamic-base"
+import copyPlugin from "rollup-plugin-copy"
export default defineConfig({
resolve: {
@@ -16,6 +17,12 @@ export default defineConfig({
legacy({
targets: ["defaults"],
}),
+ copyPlugin({
+ targets: [
+ { src: "node_modules/libass-wasm/**/*.wasm", dest: "dist/assets" },
+ ],
+ hook: "writeBundle",
+ }),
dynamicBase({
// dynamic public path var string, default window.__dynamic_base__
publicPath: " window.__dynamic_base__",