支持高级字幕(ass,ssa)渲染

This commit is contained in:
mtvpls
2026-06-13 20:34:26 +08:00
parent e20d5bf77e
commit c45fdded59
8 changed files with 212 additions and 21 deletions
+1
View File
@@ -42,6 +42,7 @@
"artplayer": "^5.4.0",
"artplayer-plugin-auto-thumbnail": "^1.1.0",
"artplayer-plugin-danmuku": "^5.2.0",
"artplayer-plugin-jassub": "1.1.0",
"better-sqlite3": "^12.6.2",
"bs58": "^6.0.0",
"cheerio": "^1.1.2",
+8
View File
@@ -59,6 +59,9 @@ importers:
artplayer-plugin-danmuku:
specifier: ^5.2.0
version: 5.2.0
artplayer-plugin-jassub:
specifier: 1.1.0
version: 1.1.0
better-sqlite3:
specifier: ^12.6.2
version: 12.6.2
@@ -3802,6 +3805,9 @@ packages:
[email protected]:
resolution: {integrity: sha512-iHHSGEc0J+gl7imX4nmLqqC7LXmv8xeGf2lUevhhyUCoiCRZplecpKQICHjErVb1Yqyp1xCy6Sp8eYN+EpFWFw==}
[email protected]:
resolution: {integrity: sha512-vQwEtZMmkwr5utbxfUAeE/gRccl2nfcz+SYP2Subn/L8cVIFdMXWMgicm7ZkPVtAoBbCav7p0yP4pwXkCKMzAg==}
[email protected]:
resolution: {integrity: sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==}
@@ -13333,6 +13339,8 @@ snapshots:
[email protected]: {}
[email protected]: {}
[email protected]:
dependencies:
option-validator: 2.0.6
Binary file not shown.
Binary file not shown.
Binary file not shown.
+11
View File
@@ -0,0 +1,11 @@
"use strict";async function Module(i={}){var g,v=i,y=(...t)=>console.log(...t),h=(...t)=>console.error(...t);function T(){j?.(v)}G=(t=>()=>{t(),self.wasmMemory=B,self.HEAPU8C=new Uint8ClampedArray(B.buffer),self.HEAPU8=new Uint8Array(B.buffer)})(G);function P(t){throw t}var j,w,C,A,M,E,O,ie,oe,le=!1;function G(){var t=B.buffer;w=new Int8Array(t),A=new Int16Array(t),C=new Uint8Array(t),M=new Uint16Array(t),E=new Int32Array(t),O=new Uint32Array(t),ie=new Float32Array(t),oe=new Float64Array(t)}function $e(){{var t=62914560;B=new WebAssembly.Memory({initial:t/65536,maximum:32768})}G()}$e();var X=t=>Xe(t),Q=()=>Qe(),B,fe=new TextDecoder,Z=(t,r,n,s)=>{var o=r+n;if(s)return o;for(;t[r]&&!(r>=o);)++r;return r},q=(t,r,n)=>{if(!t)return"";var s=Z(C,t,r,n);return fe.decode(C.subarray(t,s))},Ce=(t,r,n,s)=>P(`Assertion failed: ${q(t)}, at: `+[r?q(r):"unknown filename",n,s?q(s):"unknown function"]);function _e(t,r,n){return 0}var we=(t,r,n)=>{};function tr(t,r,n){return 0}function nr(t,r,n,s){}var ar=()=>P(""),sr=(t,r,n,s,o)=>{},U=t=>{for(var r="";;){var n=C[t++];if(!n)return r;r+=String.fromCharCode(n)}},J={},K={},ue={},N=class extends Error{constructor(r){super(r),this.name="BindingError"}},$=t=>{throw new N(t)};function ir(t,r,n={}){var s=r.name;if(t||$(`type "${s}" must have a positive integer typeid pointer`),K.hasOwnProperty(t)){if(n.ignoreDuplicateRegistrations)return;$(`Cannot register type '${s}' twice`)}if(K[t]=r,delete ue[t],J.hasOwnProperty(t)){var o=J[t];delete J[t],o.forEach(l=>l())}}function L(t,r,n={}){return ir(t,r,n)}var or=(t,r,n,s)=>{r=U(r),L(t,{name:r,fromWireType:function(o){return!!o},toWireType:function(o,l){return l?n:s},readValueFromPointer:function(o){return this.fromWireType(C[o])},destructorFunction:null})},lr=t=>({count:t.count,deleteScheduled:t.deleteScheduled,preservePointerOnDelete:t.preservePointerOnDelete,ptr:t.ptr,ptrType:t.ptrType,smartPtr:t.smartPtr,smartPtrType:t.smartPtrType}),Te=t=>{function r(n){return n.$$.ptrType.registeredClass.name}$(r(t)+" instance already deleted")},Pe=!1,Me=t=>{},fr=t=>{t.smartPtr?t.smartPtrType.rawDestructor(t.smartPtr):t.ptrType.registeredClass.rawDestructor(t.ptr)},Re=t=>{t.count.value-=1;var r=t.count.value===0;r&&fr(t)},ee=t=>globalThis.FinalizationRegistry?(Pe=new FinalizationRegistry(r=>{Re(r.$$)}),ee=r=>{var n=r.$$,s=!!n.smartPtr;if(s){var o={$$:n};Pe.register(r,o,r)}return r},Me=r=>Pe.unregister(r),ee(t)):(ee=r=>r,t),ce=[],ur=()=>{for(;ce.length;){var t=ce.pop();t.$$.deleteScheduled=!1,t.delete()}},Ee,cr=()=>{let t=de.prototype;Object.assign(t,{isAliasOf(n){if(!(this instanceof de)||!(n instanceof de))return!1;var s=this.$$.ptrType.registeredClass,o=this.$$.ptr;n.$$=n.$$;for(var l=n.$$.ptrType.registeredClass,u=n.$$.ptr;s.baseClass;)o=s.upcast(o),s=s.baseClass;for(;l.baseClass;)u=l.upcast(u),l=l.baseClass;return s===l&&o===u},clone(){if(this.$$.ptr||Te(this),this.$$.preservePointerOnDelete)return this.$$.count.value+=1,this;var n=ee(Object.create(Object.getPrototypeOf(this),{$$:{value:lr(this.$$)}}));return n.$$.count.value+=1,n.$$.deleteScheduled=!1,n},delete(){this.$$.ptr||Te(this),this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete&&$("Object already scheduled for deletion"),Me(this),Re(this.$$),this.$$.preservePointerOnDelete||(this.$$.smartPtr=void 0,this.$$.ptr=void 0)},isDeleted(){return!this.$$.ptr},deleteLater(){return this.$$.ptr||Te(this),this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete&&$("Object already scheduled for deletion"),ce.push(this),ce.length===1&&Ee&&Ee(ur),this.$$.deleteScheduled=!0,this}});const r=Symbol.dispose;r&&(t[r]=t.delete)};function de(){}var ke=(t,r)=>Object.defineProperty(r,"name",{value:t}),We={},Ue=(t,r,n)=>{if(t[r].overloadTable===void 0){var s=t[r];t[r]=function(...o){return t[r].overloadTable.hasOwnProperty(o.length)||$(`Function '${n}' called with an invalid number of arguments (${o.length}) - expects one of (${t[r].overloadTable})!`),t[r].overloadTable[o.length].apply(this,o)},t[r].overloadTable=[],t[r].overloadTable[s.argCount]=s}},dr=(t,r,n)=>{v.hasOwnProperty(t)?((n===void 0||v[t].overloadTable!==void 0&&v[t].overloadTable[n]!==void 0)&&$(`Cannot register public name '${t}' twice`),Ue(v,t,t),v[t].overloadTable.hasOwnProperty(n)&&$(`Cannot register multiple overloads of a function with the same number of arguments (${n})!`),v[t].overloadTable[n]=r):(v[t]=r,v[t].argCount=n)},vr=48,pr=57,gr=t=>{t=t.replace(/[^a-zA-Z0-9_]/g,"$");var r=t.charCodeAt(0);return r>=vr&&r<=pr?`_${t}`:t};function br(t,r,n,s,o,l,u,f){this.name=t,this.constructor=r,this.instancePrototype=n,this.rawDestructor=s,this.baseClass=o,this.getActualType=l,this.upcast=u,this.downcast=f,this.pureVirtualFunctions=[]}var ve=(t,r,n)=>{for(;r!==n;)r.upcast||$(`Expected null or instance of ${n.name}, got an instance of ${r.name}`),t=r.upcast(t),r=r.baseClass;return t},je=t=>{if(t===null)return"null";var r=typeof t;return r==="object"||r==="array"||r==="function"?t.toString():""+t};function hr(t,r){if(r===null)return this.isReference&&$(`null is not a valid ${this.name}`),0;r.$$||$(`Cannot pass "${je(r)}" as a ${this.name}`),r.$$.ptr||$(`Cannot pass deleted object as a pointer of type ${this.name}`);var n=r.$$.ptrType.registeredClass,s=ve(r.$$.ptr,n,this.registeredClass);return s}function yr(t,r){var n;if(r===null)return this.isReference&&$(`null is not a valid ${this.name}`),this.isSmartPointer?(n=this.rawConstructor(),t!==null&&t.push(this.rawDestructor,n),n):0;(!r||!r.$$)&&$(`Cannot pass "${je(r)}" as a ${this.name}`),r.$$.ptr||$(`Cannot pass deleted object as a pointer of type ${this.name}`),!this.isConst&&r.$$.ptrType.isConst&&$(`Cannot convert argument of type ${r.$$.smartPtrType?r.$$.smartPtrType.name:r.$$.ptrType.name} to parameter type ${this.name}`);var s=r.$$.ptrType.registeredClass;if(n=ve(r.$$.ptr,s,this.registeredClass),this.isSmartPointer)switch(r.$$.smartPtr===void 0&&$("Passing raw pointer to smart pointer is illegal"),this.sharingPolicy){case 0:r.$$.smartPtrType===this?n=r.$$.smartPtr:$(`Cannot convert argument of type ${r.$$.smartPtrType?r.$$.smartPtrType.name:r.$$.ptrType.name} to parameter type ${this.name}`);break;case 1:n=r.$$.smartPtr;break;case 2:if(r.$$.smartPtrType===this)n=r.$$.smartPtr;else{var o=r.clone();n=this.rawShare(n,Fe.toHandle(()=>o.delete())),t!==null&&t.push(this.rawDestructor,n)}break;default:$("Unsupporting sharing policy")}return n}function mr(t,r){if(r===null)return this.isReference&&$(`null is not a valid ${this.name}`),0;r.$$||$(`Cannot pass "${je(r)}" as a ${this.name}`),r.$$.ptr||$(`Cannot pass deleted object as a pointer of type ${this.name}`),r.$$.ptrType.isConst&&$(`Cannot convert argument of type ${r.$$.ptrType.name} to parameter type ${this.name}`);var n=r.$$.ptrType.registeredClass,s=ve(r.$$.ptr,n,this.registeredClass);return s}function pe(t){return this.fromWireType(O[t>>2])}var De=(t,r,n)=>{if(r===n)return t;if(n.baseClass===void 0)return null;var s=De(t,r,n.baseClass);return s===null?null:n.downcast(s)},$r={},Cr=(t,r)=>{for(r===void 0&&$("ptr should not be undefined");t.baseClass;)r=t.upcast(r),t=t.baseClass;return r},_r=(t,r)=>(r=Cr(t,r),$r[r]),wr=class extends Error{constructor(r){super(r),this.name="InternalError"}},ge=t=>{throw new wr(t)},be=(t,r)=>{(!r.ptrType||!r.ptr)&&ge("makeClassHandle requires ptr and ptrType");var n=!!r.smartPtrType,s=!!r.smartPtr;return n!==s&&ge("Both smartPtrType and smartPtr must be specified"),r.count={value:1},ee(Object.create(t,{$$:{value:r,writable:!0}}))};function Tr(t){var r=this.getPointee(t);if(!r)return this.destructor(t),null;var n=_r(this.registeredClass,r);if(n!==void 0){if(n.$$.count.value===0)return n.$$.ptr=r,n.$$.smartPtr=t,n.clone();var s=n.clone();return this.destructor(t),s}function o(){return this.isSmartPointer?be(this.registeredClass.instancePrototype,{ptrType:this.pointeeType,ptr:r,smartPtrType:this,smartPtr:t}):be(this.registeredClass.instancePrototype,{ptrType:this,ptr:t})}var l=this.registeredClass.getActualType(r),u=We[l];if(!u)return o.call(this);var f;this.isConst?f=u.constPointerType:f=u.pointerType;var p=De(r,this.registeredClass,f.registeredClass);return p===null?o.call(this):this.isSmartPointer?be(f.registeredClass.instancePrototype,{ptrType:f,ptr:p,smartPtrType:this,smartPtr:t}):be(f.registeredClass.instancePrototype,{ptrType:f,ptr:p})}var Pr=()=>{Object.assign(he.prototype,{getPointee(t){return this.rawGetPointee&&(t=this.rawGetPointee(t)),t},destructor(t){this.rawDestructor?.(t)},readValueFromPointer:pe,fromWireType:Tr})};function he(t,r,n,s,o,l,u,f,p,m,_){this.name=t,this.registeredClass=r,this.isReference=n,this.isConst=s,this.isSmartPointer=o,this.pointeeType=l,this.sharingPolicy=u,this.rawGetPointee=f,this.rawConstructor=p,this.rawShare=m,this.rawDestructor=_,!o&&r.baseClass===void 0?s?(this.toWireType=hr,this.destructorFunction=null):(this.toWireType=mr,this.destructorFunction=null):this.toWireType=yr}var jr=(t,r,n)=>{v.hasOwnProperty(t)||ge("Replacing nonexistent public symbol"),v[t].overloadTable!==void 0&&n!==void 0?v[t].overloadTable[n]=r:(v[t]=r,v[t].argCount=n)},ye={},Fr=(t,r,n)=>{t=t.replace(/p/g,"i");var s=ye[t];return s(r,...n)},Ie=[],re=t=>{var r=Ie[t];return r||(Ie[t]=r=Ze.get(t)),r},Ar=(t,r,n=[],s=!1)=>{if(t.includes("j"))return Fr(t,r,n);var o=re(r),l=o(...n);function u(f){return f}return l},Sr=(t,r,n=!1)=>(...s)=>Ar(t,r,s,n),H=(t,r,n=!1)=>{t=U(t);function s(){if(t.includes("j"))return Sr(t,r);var l=re(r);return l}var o=s();return typeof o!="function"&&$(`unknown function pointer with signature ${t}: ${r}`),o};class Or extends Error{}var Mr=t=>{var r=Ke(t),n=U(r);return V(r),n},te=(t,r)=>{var n=[],s={};function o(l){if(!s[l]&&!K[l]){if(ue[l]){ue[l].forEach(o);return}n.push(l),s[l]=!0}}throw r.forEach(o),new Or(`${t}: `+n.map(Mr).join([", "]))},z=(t,r,n)=>{t.forEach(f=>ue[f]=r);function s(f){var p=n(f);p.length!==t.length&&ge("Mismatched type converter count");for(var m=0;m<t.length;++m)L(t[m],p[m])}var o=new Array(r.length),l=[],u=0;for(let[f,p]of r.entries())K.hasOwnProperty(p)?o[f]=K[p]:(l.push(p),J.hasOwnProperty(p)||(J[p]=[]),J[p].push(()=>{o[f]=K[p],++u,u===l.length&&s(o)}));l.length===0&&s(o)},Rr=(t,r,n,s,o,l,u,f,p,m,_,S,R)=>{_=U(_),l=H(o,l),f&&=H(u,f),m&&=H(p,m),R=H(S,R);var F=gr(_);dr(F,function(){te(`Cannot construct ${_} due to unbound types`,[s])}),z([t,r,n],s?[s]:[],k=>{k=k[0];var D,x;s?(D=k.registeredClass,x=D.instancePrototype):x=de.prototype;var I=ke(_,function(...Oe){if(Object.getPrototypeOf(this)!==se)throw new N(`Use 'new' to construct ${_}`);if(W.constructor_body===void 0)throw new N(`${_} has no accessible constructor`);var rr=W.constructor_body[Oe.length];if(rr===void 0)throw new N(`Tried to invoke ctor of ${_} with invalid number of parameters (${Oe.length}) - expected (${Object.keys(W.constructor_body).toString()}) parameters instead!`);return rr.apply(this,Oe)}),se=Object.create(x,{constructor:{value:I}});I.prototype=se;var W=new br(_,I,se,R,D,l,f,m);W.baseClass&&(W.baseClass.__derivedClasses??=[],W.baseClass.__derivedClasses.push(W));var Ot=new he(_,W,!0,!1,!1),Ne=new he(_+"*",W,!1,!1,!1),er=new he(_+" const*",W,!1,!0,!1);return We[t]={pointerType:Ne,constPointerType:er},jr(F,I),[Ot,Ne,er]})},Be=(t,r)=>{for(var n=[],s=0;s<t;s++)n.push(O[r+s*4>>2]);return n},Le=t=>{for(;t.length;){var r=t.pop(),n=t.pop();n(r)}};function xe(t){for(var r=1;r<t.length;++r)if(t[r]!==null&&t[r].destructorFunction===void 0)return!0;return!1}function Er(t,r,n,s){var o=xe(t),l=t.length-2,u=[],f=["fn"];r&&f.push("thisWired");for(var p=0;p<l;++p)u.push(`arg${p}`),f.push(`arg${p}Wired`);u=u.join(","),f=f.join(",");var m=`return function (${u}) {
`;o&&(m+=`var destructors = [];
`);var _=o?"destructors":"null",S=["humanName","throwBindingError","invoker","fn","runDestructors","fromRetWire","toClassParamWire"];r&&(m+=`var thisWired = toClassParamWire(${_}, this);
`);for(var p=0;p<l;++p){var R=`toArg${p}Wire`;m+=`var arg${p}Wired = ${R}(${_}, arg${p});
`,S.push(R)}if(m+=(n||s?"var rv = ":"")+`invoker(${f});
`,o)m+=`runDestructors(destructors);
`;else for(var p=r?1:2;p<t.length;++p){var F=p===1?"thisWired":"arg"+(p-2)+"Wired";t[p].destructorFunction!==null&&(m+=`${F}_dtor(${F});
`,S.push(`${F}_dtor`))}return n&&(m+=`var ret = fromRetWire(rv);
return ret;
`),m+=`}
`,new Function(S,m)}function He(t,r,n,s,o,l){var u=r.length;u<2&&$("argTypes array size mismatch! Must at least get return value and 'this' types!");for(var f=r[1]!==null&&n!==null,p=xe(r),m=!r[0].isVoid,_=r[0],S=r[1],R=[t,$,s,o,Le,_.fromWireType.bind(_),S?.toWireType.bind(S)],F=2;F<u;++F){var k=r[F];R.push(k.toWireType.bind(k))}if(!p)for(var F=f?1:2;F<r.length;++F)r[F].destructorFunction!==null&&R.push(r[F].destructorFunction);var x=Er(r,f,m,l)(...R);return ke(t,x)}var kr=(t,r,n,s,o,l)=>{var u=Be(r,n);o=H(s,o),z([],[t],f=>{f=f[0];var p=`constructor ${f.name}`;if(f.registeredClass.constructor_body===void 0&&(f.registeredClass.constructor_body=[]),f.registeredClass.constructor_body[r-1]!==void 0)throw new N(`Cannot register multiple constructors with identical number of parameters (${r-1}) for class '${f.name}'! Overload resolution is currently only performed using the parameter count, not actual type info!`);return f.registeredClass.constructor_body[r-1]=()=>{te(`Cannot construct ${f.name} due to unbound types`,u)},z([],u,m=>(m.splice(1,0,null),f.registeredClass.constructor_body[r-1]=He(p,m,null,o,l),[])),[]})},Wr=t=>{t=t.trim();const r=t.indexOf("(");return r===-1?t:t.slice(0,r)},Ur=(t,r,n,s,o,l,u,f,p,m)=>{var _=Be(n,s);r=U(r),r=Wr(r),l=H(o,l,p),z([],[t],S=>{S=S[0];var R=`${S.name}.${r}`;r.startsWith("@@")&&(r=Symbol[r.substring(2)]),f&&S.registeredClass.pureVirtualFunctions.push(r);function F(){te(`Cannot call ${R} due to unbound types`,_)}var k=S.registeredClass.instancePrototype,D=k[r];return D===void 0||D.overloadTable===void 0&&D.className!==S.name&&D.argCount===n-2?(F.argCount=n-2,F.className=S.name,k[r]=F):(Ue(k,r,R),k[r].overloadTable[n-2]=F),z([],_,x=>{var I=He(R,x,S,l,u,p);return k[r].overloadTable===void 0?(I.argCount=n-2,k[r]=I):k[r].overloadTable[n-2]=I,[]}),[]})},Ve=(t,r,n)=>(t instanceof Object||$(`${n} with invalid "this": ${t}`),t instanceof r.registeredClass.constructor||$(`${n} incompatible with "this" of type ${t.constructor.name}`),t.$$.ptr||$(`cannot call emscripten binding method ${n} on deleted object`),ve(t.$$.ptr,t.$$.ptrType.registeredClass,r.registeredClass)),Dr=(t,r,n,s,o,l,u,f,p,m)=>{r=U(r),o=H(s,o),z([],[t],_=>{_=_[0];var S=`${_.name}.${r}`,R={get(){te(`Cannot access ${S} due to unbound types`,[n,u])},enumerable:!0,configurable:!0};return p?R.set=()=>te(`Cannot access ${S} due to unbound types`,[n,u]):R.set=F=>$(S+" is a read-only property"),Object.defineProperty(_.registeredClass.instancePrototype,r,R),z([],p?[n,u]:[n],F=>{var k=F[0],D={get(){var I=Ve(this,_,S+" getter");return k.fromWireType(o(l,I))},enumerable:!0};if(p){p=H(f,p);var x=F[1];D.set=function(I){var se=Ve(this,_,S+" setter"),W=[];p(m,se,x.toWireType(W,I)),Le(W)}}return Object.defineProperty(_.registeredClass.instancePrototype,r,D),[]}),[]})},ze=[],Y=[0,1,,1,null,1,!0,1,!1,1],Ir=t=>{t>9&&--Y[t+1]===0&&(Y[t]=void 0,ze.push(t))},Fe={toValue:t=>(t||$(`Cannot use deleted val. handle = ${t}`),Y[t]),toHandle:t=>{switch(t){case void 0:return 2;case null:return 4;case!0:return 6;case!1:return 8;default:{const r=ze.pop()||Y.length;return Y[r]=t,Y[r+1]=1,r}}}},Br={name:"emscripten::val",fromWireType:t=>{var r=Fe.toValue(t);return Ir(t),r},toWireType:(t,r)=>Fe.toHandle(r),readValueFromPointer:pe,destructorFunction:null},Lr=t=>L(t,Br),xr=(t,r)=>{switch(r){case 4:return function(n){return this.fromWireType(ie[n>>2])};case 8:return function(n){return this.fromWireType(oe[n>>3])};default:throw new TypeError(`invalid float width (${r}): ${t}`)}},Hr=(t,r,n)=>{r=U(r),L(t,{name:r,fromWireType:s=>s,toWireType:(s,o)=>o,readValueFromPointer:xr(r,n),destructorFunction:null})},Vr=(t,r,n)=>{switch(r){case 1:return n?s=>w[s]:s=>C[s];case 2:return n?s=>A[s>>1]:s=>M[s>>1];case 4:return n?s=>E[s>>2]:s=>O[s>>2];default:throw new TypeError(`invalid integer width (${r}): ${t}`)}},zr=(t,r,n,s,o)=>{r=U(r);const l=s===0;let u=p=>p;if(l){var f=32-8*n;u=p=>p<<f>>>f,o=u(o)}L(t,{name:r,fromWireType:u,toWireType:(p,m)=>m,readValueFromPointer:Vr(r,n,s!==0),destructorFunction:null})},Gr=(t,r,n)=>{var s=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array],o=s[r];function l(u){var f=O[u>>2],p=O[u+4>>2];return new o(w.buffer,p,f)}n=U(n),L(t,{name:n,fromWireType:l,readValueFromPointer:l},{ignoreDuplicateRegistrations:!0})},qr=(t,r,n,s)=>{if(!(s>0))return 0;for(var o=n,l=n+s-1,u=0;u<t.length;++u){var f=t.codePointAt(u);if(f<=127){if(n>=l)break;r[n++]=f}else if(f<=2047){if(n+1>=l)break;r[n++]=192|f>>6,r[n++]=128|f&63}else if(f<=65535){if(n+2>=l)break;r[n++]=224|f>>12,r[n++]=128|f>>6&63,r[n++]=128|f&63}else{if(n+3>=l)break;r[n++]=240|f>>18,r[n++]=128|f>>12&63,r[n++]=128|f>>6&63,r[n++]=128|f&63,u++}}return r[n]=0,n-o},Ge=(t,r,n)=>qr(t,C,r,n),qe=t=>{for(var r=0,n=0;n<t.length;++n){var s=t.charCodeAt(n);s<=127?r++:s<=2047?r+=2:s>=55296&&s<=57343?(r+=4,++n):r+=3}return r},Jr=(t,r)=>{r=U(r),L(t,{name:r,fromWireType(n){var s=O[n>>2],o=n+4,l;return l=q(o,s,!0),V(n),l},toWireType(n,s){s instanceof ArrayBuffer&&(s=new Uint8Array(s));var o,l=typeof s=="string";l||ArrayBuffer.isView(s)&&s.BYTES_PER_ELEMENT==1||$("Cannot pass non-string to std::string"),l?o=qe(s):o=s.length;var u=Se(4+o+1),f=u+4;return O[u>>2]=o,l?Ge(s,f,o+1):C.set(s,f),n!==null&&n.push(V,u),u},readValueFromPointer:pe,destructorFunction(n){V(n)}})},Kr=new TextDecoder("utf-16le"),Yr=(t,r,n)=>{var s=t>>1,o=Z(M,s,r/2,n);return Kr.decode(M.subarray(s,o))},Xr=(t,r,n)=>{if(n??=2147483647,n<2)return 0;n-=2;for(var s=r,o=n<t.length*2?n/2:t.length,l=0;l<o;++l){var u=t.charCodeAt(l);A[r>>1]=u,r+=2}return A[r>>1]=0,r-s},Qr=t=>t.length*2,Zr=(t,r,n)=>{for(var s="",o=t>>2,l=0;!(l>=r/4);l++){var u=O[o+l];if(!u&&!n)break;s+=String.fromCodePoint(u)}return s},Nr=(t,r,n)=>{if(n??=2147483647,n<4)return 0;for(var s=r,o=s+n-4,l=0;l<t.length;++l){var u=t.codePointAt(l);if(u>65535&&l++,E[r>>2]=u,r+=4,r+4>o)break}return E[r>>2]=0,r-s},et=t=>{for(var r=0,n=0;n<t.length;++n){var s=t.codePointAt(n);s>65535&&n++,r+=4}return r},rt=(t,r,n)=>{n=U(n);var s,o,l;r===2?(s=Yr,o=Xr,l=Qr):(s=Zr,o=Nr,l=et),L(t,{name:n,fromWireType:u=>{var f=O[u>>2],p=s(u+4,f*r,!0);return V(u),p},toWireType:(u,f)=>{typeof f!="string"&&$(`Cannot pass non-string to C++ string type ${n}`);var p=l(f),m=Se(4+p+r);return O[m>>2]=p/r,o(f,m+4,p+r),u!==null&&u.push(V,m),m},readValueFromPointer:pe,destructorFunction(u){V(u)}})},tt=(t,r)=>{r=U(r),L(t,{isVoid:!0,name:r,fromWireType:()=>{},toWireType:(n,s)=>{}})},nt=()=>{},at=()=>{throw 1/0},ne={},st=t=>{t()},it=()=>performance.now(),ot=(t,r)=>{if(ne[t]&&(clearTimeout(ne[t].id),delete ne[t]),!r)return 0;var n=setTimeout(()=>{delete ne[t],st(()=>Ye(t,it()))},r);return ne[t]={id:n,timeout_ms:r},0},lt=()=>2147483648,ft=(t,r)=>Math.ceil(t/r)*r,ut=t=>{var r=B.buffer.byteLength,n=(t-r+65535)/65536|0;try{return B.grow(n),G(),1}catch{}},ct=t=>{var r=C.length;t>>>=0;var n=lt();if(t>n)return!1;for(var s=1;s<=4;s*=2){var o=r*(1+.2/s);o=Math.min(o,t+100663296);var l=Math.min(n,ft(Math.max(t,o),65536)),u=ut(l);if(u)return!0}return!1},Ae={},dt=()=>"./this.program",ae=()=>{if(!ae.strings){var t=(globalThis.navigator?.language??"C").replace("-","_")+".UTF-8",r={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:t,_:dt()};for(var n in Ae)Ae[n]===void 0?delete r[n]:r[n]=Ae[n];var s=[];for(var n in r)s.push(`${n}=${r[n]}`);ae.strings=s}return ae.strings},vt=(t,r)=>{var n=0,s=0;for(var o of ae()){var l=r+n;O[t+s>>2]=l,n+=Ge(o,l,1/0)+1,s+=4}return 0},pt=(t,r)=>{var n=ae();O[t>>2]=n.length;var s=0;for(var o of n)s+=qe(o)+1;return O[r>>2]=s,0},Je=t=>{throw`exit(${t})`},gt=Je,bt=t=>52,ht=(t,r,n,s)=>52;function yt(t,r,n,s,o){return 70}var mt=[null,[],[]],$t=(t,r=0,n,s)=>{var o=Z(t,r,n,s);return fe.decode(t.buffer?t.subarray(r,o):new Uint8Array(t.slice(r,o)))},Ct=(t,r)=>{var n=mt[t];r===0||r===10?((t===1?y:h)($t(n)),n.length=0):n.push(r)},_t=(t,r,n,s)=>{for(var o=0,l=0;l<n;l++){var u=O[r>>2],f=O[r+4>>2];r+=8;for(var p=0;p<f;p++)Ct(t,C[u+p]);o+=f}return O[s>>2]=o,0};cr(),Pr();var Se,V,Ke,Ye,me,Xe,Qe,Ze;function wt(t){Se=v._malloc=t.K,V=t.L,Ke=t.M,Ye=t.O,me=t.P,Xe=t.Q,Qe=t.R,ye.viiiiji=t.S,ye.jiji=t.T,ye.jij=t.U,Ze=t.N}var Tt={b:Ce,k:_e,B:we,H:tr,l:nr,z:ar,r:sr,o:or,h:Rr,E:kr,d:Ur,c:Dr,I:Lr,m:Hr,f:zr,e:Gr,n:Jr,i:rt,p:tt,x:nt,v:at,y:ot,A:ct,C:vt,D:pt,j:gt,g:bt,G:ht,q:yt,F:_t,u:Pt,s:Ft,t:jt,a:B,w:Je};function Pt(t,r,n){var s=Q();try{return re(t)(r,n)}catch(o){if(X(s),o!==o+0)throw o;me(1,0)}}function jt(t,r,n,s,o){var l=Q();try{return re(t)(r,n,s,o)}catch(u){if(X(l),u!==u+0)throw u;me(1,0)}}function Ft(t,r,n,s){var o=Q();try{return re(t)(r,n,s)}catch(l){if(X(o),l!==l+0)throw l;me(1,0)}}function At(t){le=!0,t.J()}var St={a:Tt};return WebAssembly.instantiateStreaming(fetch("jassub-worker-modern.wasm"),St).then(t=>{var r=t.instance.exports;wt(r),At(r),T()}),le?g=v:g=new Promise((t,r)=>{j=t}),g}const read_=(i,g)=>{const v=new XMLHttpRequest;return v.open("GET",i,!1),v.responseType=g?"arraybuffer":"text",v.send(null),v.response},readAsync=(i,g,v)=>{const y=new XMLHttpRequest;y.open("GET",i,!0),y.responseType="arraybuffer",y.onload=()=>{if((y.status===200||y.status===0)&&y.response)return g(y.response)},y.onerror=v,y.send(null)};let lastCurrentTime=0;const rate=1;let rafId=null,nextIsRaf=!1,lastCurrentTimeReceivedAt=Date.now(),targetFps=24,useLocalFonts=!1,blendMode="js",availableFonts={};const fontMap_={};let fontId=0,debug;self.width=0;self.height=0;let asyncRender=!1;self.addFont=({font:i})=>asyncWrite(i);const findAvailableFonts=i=>{i=i.trim().toLowerCase(),i[0]==="@"&&(i=i.substring(1)),!fontMap_[i]&&(fontMap_[i]=!0,availableFonts[i]?asyncWrite(availableFonts[i]):useLocalFonts&&postMessage({target:"getLocalFont",font:i}))},asyncWrite=i=>{typeof i=="string"?readAsync(i,g=>{allocFont(new Uint8Array(g))},console.error):allocFont(i)},allocFont=i=>{const g=_malloc(i.byteLength);self.HEAPU8.set(i,g),jassubObj.addFont("font-"+fontId++,g,i.byteLength),jassubObj.reloadFonts()},processAvailableFonts=i=>{if(!availableFonts)return;const g=parseAss(i);for(let h=0;h<g.length;h++)for(let T=0;T<g[h].body.length;T++)g[h].body[T].key==="Style"&&findAvailableFonts(g[h].body[T].value.Fontname);const v=/\\fn([^\\}]*?)[\\}]/g;let y;for(;(y=v.exec(i))!==null;)findAvailableFonts(y[1])};self.setTrack=({content:i})=>{processAvailableFonts(i),dropAllBlur&&(i=dropBlur(i)),jassubObj.createTrackMem(i),subtitleColorSpace=libassYCbCrMap[jassubObj.trackColorSpace],postMessage({target:"verifyColorSpace",subtitleColorSpace})};self.getColorSpace=()=>postMessage({target:"verifyColorSpace",subtitleColorSpace});self.freeTrack=()=>{jassubObj.removeTrack()};self.setTrackByUrl=({url:i})=>{self.setTrack({content:read_(i)})};const getCurrentTime=()=>{const i=(Date.now()-lastCurrentTimeReceivedAt)/1e3;return _isPaused?lastCurrentTime:(i>5&&(console.error("Didn't received currentTime > 5 seconds. Assuming video was paused."),setIsPaused(!0)),lastCurrentTime+i*rate)},setCurrentTime=i=>{lastCurrentTime=i,lastCurrentTimeReceivedAt=Date.now(),rafId||(nextIsRaf?rafId=requestAnimationFrame(renderLoop):(renderLoop(),setTimeout(()=>{nextIsRaf=!1},20)))};let _isPaused=!0;const setIsPaused=i=>{i!==_isPaused&&(_isPaused=i,i?rafId&&(clearTimeout(rafId),rafId=null):(lastCurrentTimeReceivedAt=Date.now(),rafId=requestAnimationFrame(renderLoop)))},a="BT601",b="BT709",c="SMPTE240M",d="FCC",libassYCbCrMap=[null,a,null,a,a,b,b,c,c,d,d],render=(i,g)=>{const v={},y=performance.now(),h=blendMode==="wasm"?jassubObj.renderBlend(i,g||0):jassubObj.renderImage(i,g||0);if(debug){const T=performance.now();v.WASMRenderTime=T-y,v.JSRenderTime=performance.now()}if(jassubObj.changed!==0||g){const T=[],P=[];if(!h)return paintImages({images:T,buffers:P,times:v});if(asyncRender){const j=[];for(let w=h,C=0;C<jassubObj.count;w=w.next,++C){const A={w:w.w,h:w.h,x:w.x,y:w.y},M=w.image,E=hasBitmapBug?self.HEAPU8C.slice(M,M+A.w*A.h*4):self.HEAPU8C.subarray(M,M+A.w*A.h*4);j.push(createImageBitmap(new ImageData(E,A.w,A.h))),T.push(A)}Promise.all(j).then(w=>{for(let C=0;C<T.length;C++)T[C].image=w[C];debug&&(v.JSRenderTime=performance.now()-v.JSRenderTime),paintImages({images:T,buffers:w,times:v})})}else{for(let j=h,w=0;w<jassubObj.count;j=j.next,++w){const C={w:j.w,h:j.h,x:j.x,y:j.y,image:j.image};if(!offCanvasCtx){const A=self.wasmMemory.buffer.slice(j.image,j.image+j.w*j.h*4);P.push(A),C.image=A}T.push(C)}paintImages({images:T,buffers:P,times:v})}}else postMessage({target:"unbusy"})};self.demand=({time:i})=>{lastCurrentTime=i,render(i)};const renderLoop=i=>{rafId=0,render(getCurrentTime(),i),_isPaused||(rafId=requestAnimationFrame(renderLoop))},paintImages=({times:i,images:g,buffers:v})=>{const y={target:"render",asyncRender,images:g,times:i,width:self.width,height:self.height,colorSpace:subtitleColorSpace};if(offscreenRender){(offCanvas.height!==self.height||offCanvas.width!==self.width)&&(offCanvas.width=self.width,offCanvas.height=self.height),offCanvasCtx.clearRect(0,0,self.width,self.height);for(const h of g)h.image&&(asyncRender?(offCanvasCtx.drawImage(h.image,h.x,h.y),h.image.close()):(bufferCanvas.width=h.w,bufferCanvas.height=h.h,bufferCtx.putImageData(new ImageData(self.HEAPU8C.subarray(h.image,h.image+h.w*h.h*4),h.w,h.h),0,0),offCanvasCtx.drawImage(bufferCanvas,h.x,h.y)));if(offscreenRender==="hybrid"){if(!g.length)return postMessage(y);debug&&(i.bitmaps=g.length);try{const h=offCanvas.transferToImageBitmap();y.images=[{image:h,x:0,y:0}],y.asyncRender=!0,postMessage(y,[h])}catch{postMessage({target:"unbusy"})}}else{if(debug){let h=0;for(const T in i)h+=i[T];console.log("Bitmaps: "+g.length+" Total: "+(h|0)+"ms",i)}postMessage({target:"unbusy"})}}else postMessage(y,v)},parseAss=i=>{let g,v,y,h,T,P,j,w,C,A;const M=[],E=i.split(/[\r\n]+/g);for(w=0;w<E.length;w++)if(g=E[w].match(/^\[(.*)\]$/),g)v=null,M.push({name:g[1],body:[]});else{if(/^\s*$/.test(E[w])||M.length===0)continue;if(A=M[M.length-1].body,E[w][0]===";")A.push({type:"comment",value:E[w].substring(1)});else{if(h=E[w].split(":"),T=h[0],P=h.slice(1).join(":").trim(),(v||T==="Format")&&(P=P.split(","),v&&P.length>v.length&&(y=P.slice(v.length-1).join(","),P=P.slice(0,v.length-1),P.push(y)),P=P.map(O=>O.trim()),v)){for(j={},C=0;C<P.length;C++)j[v[C]]=P[C];P=j}T==="Format"&&(v=P),A.push({key:T,value:P})}}return M},blurRegex=/\\blur(?:[0-9]+\.)?[0-9]+/gm,dropBlur=i=>i.replace(blurRegex,""),requestAnimationFrame=(()=>{let i=0;return g=>{const v=Date.now();if(i===0)i=v+1e3/targetFps;else for(;v+2>=i;)i+=1e3/targetFps;const y=Math.max(i-v,0);return setTimeout(g,y)}})(),_applyKeys=(i,g)=>{for(const v of Object.keys(i))g[v]=i[v]};let offCanvas,offCanvasCtx,offscreenRender,bufferCanvas,bufferCtx,jassubObj,subtitleColorSpace,dropAllBlur,_malloc,hasBitmapBug;self.init=data=>{hasBitmapBug=data.hasBitmapBug;try{const i=new WebAssembly.Module(Uint8Array.of(0,97,115,109,1,0,0,0));if(!(i instanceof WebAssembly.Module)||!(new WebAssembly.Instance(i)instanceof WebAssembly.Instance))throw new Error("WASM not supported")}catch(e){console.warn(e),eval(read_(data.legacyWasmUrl))}if(WebAssembly.instantiateStreaming){const i=self.fetch;self.fetch=g=>i(data.wasmUrl)}Module({wasm:!WebAssembly.instantiateStreaming&&read_(data.wasmUrl,!0)}).then(i=>{_malloc=i._malloc,self.width=data.width,self.height=data.height,blendMode=data.blendMode,asyncRender=data.asyncRender,asyncRender&&typeof createImageBitmap>"u"&&(asyncRender=!1,console.error("'createImageBitmap' needed for 'asyncRender' unsupported!")),availableFonts=data.availableFonts,debug=data.debug,targetFps=data.targetFps||targetFps,useLocalFonts=data.useLocalFonts,dropAllBlur=data.dropAllBlur;const g=data.fallbackFont.toLowerCase();jassubObj=new i.JASSUB(self.width,self.height,g||null,debug),g&&findAvailableFonts(g);let v=data.subContent;v||(v=read_(data.subUrl)),processAvailableFonts(v),dropAllBlur&&(v=dropBlur(v));for(const y of data.fonts||[])asyncWrite(y);jassubObj.createTrackMem(v),subtitleColorSpace=libassYCbCrMap[jassubObj.trackColorSpace],jassubObj.setDropAnimations(data.dropAllAnimations||0),(data.libassMemoryLimit>0||data.libassGlyphLimit>0)&&jassubObj.setMemoryLimits(data.libassGlyphLimit||0,data.libassMemoryLimit||0),postMessage({target:"ready"}),postMessage({target:"verifyColorSpace",subtitleColorSpace})})};self.offscreenCanvas=({transferable:i})=>{offCanvas=i[0],offCanvasCtx=offCanvas.getContext("2d"),asyncRender||(bufferCanvas=new OffscreenCanvas(self.height,self.width),bufferCtx=bufferCanvas.getContext("2d",{desynchronized:!0})),offscreenRender=!0};self.detachOffscreen=()=>{offCanvas=new OffscreenCanvas(self.height,self.width),offCanvasCtx=offCanvas.getContext("2d",{desynchronized:!0}),offscreenRender="hybrid"};self.canvas=({width:i,height:g,videoWidth:v,videoHeight:y,force:h})=>{if(i==null)throw new Error("Invalid canvas size specified");self.width=i,self.height=g,jassubObj&&jassubObj.resizeCanvas(i,g,v,y),h&&render(lastCurrentTime,!0)};self.video=({currentTime:i,isPaused:g,rate:v})=>{i!=null&&setCurrentTime(i),g!=null&&setIsPaused(g),v=v||v};self.destroy=()=>{jassubObj.quitLibrary()};self.createEvent=({event:i})=>{_applyKeys(i,jassubObj.getEvent(jassubObj.allocEvent()))};self.getEvents=()=>{const i=[];for(let g=0;g<jassubObj.getEventCount();g++){const{Start:v,Duration:y,ReadOrder:h,Layer:T,Style:P,MarginL:j,MarginR:w,MarginV:C,Name:A,Text:M,Effect:E}=jassubObj.getEvent(g);i.push({Start:v,Duration:y,ReadOrder:h,Layer:T,Style:P,MarginL:j,MarginR:w,MarginV:C,Name:A,Text:M,Effect:E})}postMessage({target:"getEvents",events:i})};self.setEvent=({event:i,index:g})=>{_applyKeys(i,jassubObj.getEvent(g))};self.removeEvent=({index:i})=>{jassubObj.removeEvent(i)};self.createStyle=({style:i})=>{const g=jassubObj.getStyle(jassubObj.allocStyle());return _applyKeys(i,g),g};self.getStyles=()=>{const i=[];for(let g=0;g<jassubObj.getStyleCount();g++){const{Name:v,FontName:y,FontSize:h,PrimaryColour:T,SecondaryColour:P,OutlineColour:j,BackColour:w,Bold:C,Italic:A,Underline:M,StrikeOut:E,ScaleX:O,ScaleY:ie,Spacing:oe,Angle:le,BorderStyle:G,Outline:$e,Shadow:X,Alignment:Q,MarginL:B,MarginR:fe,MarginV:Z,Encoding:q,treat_fontname_as_pattern:Ce,Blur:_e,Justify:we}=jassubObj.getStyle(g);i.push({Name:v,FontName:y,FontSize:h,PrimaryColour:T,SecondaryColour:P,OutlineColour:j,BackColour:w,Bold:C,Italic:A,Underline:M,StrikeOut:E,ScaleX:O,ScaleY:ie,Spacing:oe,Angle:le,BorderStyle:G,Outline:$e,Shadow:X,Alignment:Q,MarginL:B,MarginR:fe,MarginV:Z,Encoding:q,treat_fontname_as_pattern:Ce,Blur:_e,Justify:we})}postMessage({target:"getStyles",time:Date.now(),styles:i})};self.setStyle=({style:i,index:g})=>{_applyKeys(i,jassubObj.getStyle(g))};self.removeStyle=({index:i})=>{jassubObj.removeStyle(i)};self.styleOverride=i=>{jassubObj.styleOverride(self.createStyle(i))};self.disableStyleOverride=()=>{jassubObj.disableStyleOverride()};self.defaultFont=({font:i})=>{jassubObj.setDefaultFont(i)};onmessage=({data:i})=>{if(self[i.target])self[i.target](i);else throw new Error("Unknown event target "+i.target)};
Binary file not shown.
+192 -21
View File
@@ -121,14 +121,29 @@ interface SearchCachePayload {
updatedAt: number;
}
type CustomSubtitleEngine = 'native' | 'jassub';
interface CustomSubtitleState {
name: string;
url: string;
format: string;
episodeIndex: number;
engine: CustomSubtitleEngine;
url?: string;
content?: string;
}
interface JassubSubtitleInstance {
setTrack?: (content: string) => void | Promise<void>;
setTrackByUrl?: (url: string) => void | Promise<void>;
freeTrack?: () => void | Promise<void>;
destroy?: () => void | Promise<void>;
}
const PLAYBACK_RATE_OPTIONS = [0.5, 0.75, 1, 1.25, 1.5, 2, 3, 4];
const JASSUB_ASSET_BASE = '/assets/jassub';
const JASSUB_CJK_FONT_FAMILY = 'noto sans cjk sc';
const JASSUB_CJK_FONT_URL = `${JASSUB_ASSET_BASE}/NotoSansCJK-Regular.ttc`;
const ADVANCED_SUBTITLE_FORMATS = new Set(['ass', 'ssa']);
const PLAY_SHORTCUT_GROUPS = [
{
title: '播放控制',
@@ -1933,16 +1948,43 @@ function PlayPageClient() {
fontSize: typeof window !== 'undefined' ? localStorage.getItem('subtitleSize') || '2em' : '2em',
});
const revokeCustomSubtitle = () => {
if (customSubtitleRef.current) {
URL.revokeObjectURL(customSubtitleRef.current.url);
customSubtitleRef.current = null;
const getSubtitleFileExtension = (fileName: string) => {
return fileName.toLowerCase().match(/\.([a-z0-9]+)$/)?.[1] || '';
};
const isAdvancedSubtitleFormat = (format: string) => {
return ADVANCED_SUBTITLE_FORMATS.has(format.toLowerCase());
};
const getJassubSubtitleInstance = (): JassubSubtitleInstance | null => {
return artPlayerRef.current?.plugins?.artplayerPluginJassub?.instance || null;
};
const clearJassubSubtitle = () => {
try {
getJassubSubtitleInstance()?.freeTrack?.();
} catch (error) {
console.warn('[Subtitle] 清理高级字幕失败:', error);
}
};
const revokeCustomSubtitle = () => {
const customSubtitle = customSubtitleRef.current;
if (customSubtitle?.engine === 'native' && customSubtitle.url) {
URL.revokeObjectURL(customSubtitle.url);
}
if (customSubtitle?.engine === 'jassub') {
clearJassubSubtitle();
}
customSubtitleRef.current = null;
};
const switchSubtitle = (url: string, label: string) => {
if (!artPlayerRef.current) return;
clearJassubSubtitle();
artPlayerRef.current.subtitle.switch(url, {
name: label,
type: 'vtt',
@@ -1953,6 +1995,66 @@ function PlayPageClient() {
currentSubtitleLabelRef.current = label;
};
const closeSubtitle = () => {
if (!artPlayerRef.current) return;
artPlayerRef.current.subtitle.show = false;
clearJassubSubtitle();
currentSubtitleLabelRef.current = '关闭';
};
const ensureJassubSubtitleInstance = async (
initialContent: string
): Promise<{ instance: JassubSubtitleInstance; created: boolean }> => {
const existingInstance = getJassubSubtitleInstance();
if (existingInstance) {
return { instance: existingInstance, created: false };
}
if (!artPlayerRef.current) {
throw new Error('播放器尚未就绪');
}
const JassubPluginModule = await import('artplayer-plugin-jassub');
const artplayerPluginJassub =
((JassubPluginModule as any).default || JassubPluginModule) as any;
artPlayerRef.current.plugins.add(
artplayerPluginJassub({
subContent: initialContent,
workerUrl: `${JASSUB_ASSET_BASE}/jassub-worker.js`,
wasmUrl: `${JASSUB_ASSET_BASE}/jassub-worker.wasm`,
modernWasmUrl: `${JASSUB_ASSET_BASE}/jassub-worker-modern.wasm`,
availableFonts: {
[JASSUB_CJK_FONT_FAMILY]: JASSUB_CJK_FONT_URL,
'liberation sans': `${JASSUB_ASSET_BASE}/default.woff2`,
},
fallbackFont: JASSUB_CJK_FONT_FAMILY,
})
);
const instance = getJassubSubtitleInstance();
if (!instance) {
throw new Error('高级字幕渲染器初始化失败');
}
return { instance, created: true };
};
const switchAdvancedSubtitle = async (content: string, label: string) => {
if (!artPlayerRef.current) return;
artPlayerRef.current.subtitle.show = false;
const { instance, created } = await ensureJassubSubtitleInstance(content);
// 新建实例时 subContent 已作为初始轨道传入;复用实例时需要显式切轨。
if (!created) {
await instance.setTrack?.(content);
}
currentSubtitleLabelRef.current = label;
};
const removeSubtitleSetting = () => {
try {
artPlayerRef.current?.setting.remove('subtitle-selector');
@@ -1978,6 +2080,7 @@ function PlayPageClient() {
...sourceSubtitles.map((sub: any) => ({
html: sub.label,
action: 'switch',
engine: 'native',
url: sub.url,
})),
...(customSubtitle
@@ -1985,7 +2088,9 @@ function PlayPageClient() {
{
html: `本地:${customSubtitle.name}`,
action: 'switch',
engine: customSubtitle.engine,
url: customSubtitle.url,
content: customSubtitle.content,
},
]
: []),
@@ -2001,8 +2106,7 @@ function PlayPageClient() {
}
if (item.action === 'close') {
artPlayerRef.current.subtitle.show = false;
currentSubtitleLabelRef.current = '关闭';
closeSubtitle();
return item.html;
}
@@ -2011,6 +2115,18 @@ function PlayPageClient() {
return currentSubtitleLabelRef.current;
}
if (item.engine === 'jassub' && item.content) {
void switchAdvancedSubtitle(item.content, item.html).catch((error) => {
console.warn('[Subtitle] 高级字幕切换失败:', error);
setToast({
message: error instanceof Error ? error.message : '高级字幕切换失败',
type: 'error',
onClose: () => setToast(null),
});
});
return item.html;
}
if (item.url) {
switchSubtitle(item.url, item.html);
return item.html;
@@ -2022,6 +2138,41 @@ function PlayPageClient() {
});
};
const loadNativeCustomSubtitle = async (file: File) => {
const convertedSubtitle = await convertSubtitleFileToVttObjectUrl(file);
revokeCustomSubtitle();
customSubtitleRef.current = {
...convertedSubtitle,
engine: 'native',
episodeIndex: currentEpisodeIndexRef.current,
};
switchSubtitle(
convertedSubtitle.url,
`本地:${convertedSubtitle.name}`
);
updateSubtitleSetting();
return convertedSubtitle;
};
const loadAdvancedCustomSubtitle = async (file: File, format: string) => {
const content = await file.text();
revokeCustomSubtitle();
customSubtitleRef.current = {
name: file.name,
format,
engine: 'jassub',
content,
episodeIndex: currentEpisodeIndexRef.current,
};
await switchAdvancedSubtitle(content, `本地:${file.name}`);
updateSubtitleSetting();
};
const handleCustomSubtitleFileChange = async (
event: React.ChangeEvent<HTMLInputElement>
) => {
@@ -2030,29 +2181,49 @@ function PlayPageClient() {
if (!file) return;
const extension = getSubtitleFileExtension(file.name);
try {
const convertedSubtitle = await convertSubtitleFileToVttObjectUrl(file);
revokeCustomSubtitle();
if (isAdvancedSubtitleFormat(extension)) {
await loadAdvancedCustomSubtitle(file, extension);
setToast({
message: `已加载高级字幕:${file.name}`,
type: 'success',
onClose: () => setToast(null),
});
return;
}
customSubtitleRef.current = {
...convertedSubtitle,
episodeIndex: currentEpisodeIndexRef.current,
};
switchSubtitle(
convertedSubtitle.url,
`本地:${convertedSubtitle.name}`
);
updateSubtitleSetting();
const convertedSubtitle = await loadNativeCustomSubtitle(file);
setToast({
message: `已加载本地字幕:${convertedSubtitle.name}`,
type: 'success',
onClose: () => setToast(null),
});
} catch (error) {
console.warn('[Subtitle] 自定义字幕加载失败:', error);
let displayError = error;
if (isAdvancedSubtitleFormat(extension)) {
console.warn('[Subtitle] 高级字幕加载失败,尝试降级为普通字幕:', displayError);
try {
const convertedSubtitle = await loadNativeCustomSubtitle(file);
setToast({
message: `高级字幕渲染失败,已降级为普通字幕:${convertedSubtitle.name}`,
type: 'info',
duration: 5000,
onClose: () => setToast(null),
});
return;
} catch (fallbackError) {
console.warn('[Subtitle] 高级字幕降级加载失败:', fallbackError);
displayError = fallbackError;
}
}
console.warn('[Subtitle] 自定义字幕加载失败:', displayError);
setToast({
message: error instanceof Error ? error.message : '字幕加载失败',
message: displayError instanceof Error ? displayError.message : '字幕加载失败',
type: 'error',
onClose: () => setToast(null),
});