=k(z,p)?0>=k(x,p)?p.getFullYear()+1:p.getFullYear():p.getFullYear()-1}var q=E[d+40>>2];d={Ti:E[d>>2],Si:E[d+4>>
208 | 2],Eg:E[d+8>>2],pg:E[d+12>>2],dg:E[d+16>>2],rf:E[d+20>>2],Fg:E[d+24>>2],Gg:E[d+28>>2],yj:E[d+32>>2],Ri:E[d+36>>2],Ui:q?C(q):""};c=C(c);q={"%c":"%a %b %d %H:%M:%S %Y","%D":"%m/%d/%y","%F":"%Y-%m-%d","%h":"%b","%r":"%I:%M:%S %p","%R":"%H:%M","%T":"%H:%M:%S","%x":"%m/%d/%y","%X":"%H:%M:%S","%Ec":"%c","%EC":"%C","%Ex":"%m/%d/%y","%EX":"%H:%M:%S","%Ey":"%y","%EY":"%Y","%Od":"%d","%Oe":"%e","%OH":"%H","%OI":"%I","%Om":"%m","%OM":"%M","%OS":"%S","%Ou":"%u","%OU":"%U","%OV":"%V","%Ow":"%w","%OW":"%W","%Oy":"%y"};
209 | for(var t in q)c=c.replace(new RegExp(t,"g"),q[t]);var w="Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),B="January February March April May June July August September October November December".split(" ");q={"%a":function(p){return w[p.Fg].substring(0,3)},"%A":function(p){return w[p.Fg]},"%b":function(p){return B[p.dg].substring(0,3)},"%B":function(p){return B[p.dg]},"%C":function(p){return g((p.rf+1900)/100|0,2)},"%d":function(p){return g(p.pg,2)},"%e":function(p){return e(p.pg,
210 | 2," ")},"%g":function(p){return r(p).toString().substring(2)},"%G":function(p){return r(p)},"%H":function(p){return g(p.Eg,2)},"%I":function(p){p=p.Eg;0==p?p=12:12p.Eg?"AM":"PM"},"%S":function(p){return g(p.Ti,2)},"%t":function(){return"\t"},"%u":function(p){return p.Fg||
211 | 7},"%U":function(p){var x=new Date(p.rf+1900,0,1),z=0===x.getDay()?x:be(x,7-x.getDay());p=new Date(p.rf+1900,p.dg,p.pg);return 0>k(z,p)?g(Math.ceil((31-z.getDate()+(Zd(Yd(p.getFullYear())?$d:ae,p.getMonth()-1)-31)+p.getDate())/7),2):0===k(z,x)?"01":"00"},"%V":function(p){var x=new Date(p.rf+1901,0,4),z=m(new Date(p.rf+1900,0,4));x=m(x);var I=be(new Date(p.rf+1900,0,1),p.Gg);return 0>k(I,z)?"53":0>=k(x,I)?"01":g(Math.ceil((z.getFullYear()
k(z,p)?g(Math.ceil((31-z.getDate()+(Zd(Yd(p.getFullYear())?$d:ae,p.getMonth()-1)-31)+p.getDate())/7),2):0===k(z,x)?"01":"00"},"%y":function(p){return(p.rf+1900).toString().substring(2)},"%Y":function(p){return p.rf+1900},"%z":function(p){p=p.Ri;var x=0<=p;p=Math.abs(p)/60;return(x?"+":"-")+String("0000"+(p/60*100+p%60)).slice(-4)},"%Z":function(p){return p.Ui},
213 | "%%":function(){return"%"}};for(t in q)0<=c.indexOf(t)&&(c=c.replace(new RegExp(t,"g"),q[t](d)));t=Zb(c,!1);if(t.length>b)return 0;y.set(t,a);return t.length-1},x:ce,s:function(a){var b=Date.now()/1E3|0;a&&(E[a>>2]=b);return b}};
214 | (function(){function a(e,g){f.asm=e.exports;H=f.asm.Sd;Da=g;l||fb()}function b(e){a(e.instance,e.module)}function c(e){return kb().then(function(g){return WebAssembly.instantiate(g,d)}).then(e,function(g){u("failed to asynchronously prepare wasm: "+g);n(g)})}var d={a:Fe};l||eb();if(f.instantiateWasm)try{return f.instantiateWasm(d,a)}catch(e){return u("Module.instantiateWasm callback failed with error: "+e),!1}(function(){return Ba||"function"!==typeof WebAssembly.instantiateStreaming||ib()||gb("file://")||
215 | "function"!==typeof fetch?c(b):fetch(hb,{credentials:"same-origin"}).then(function(e){return WebAssembly.instantiateStreaming(e,d).then(b,function(g){u("wasm streaming compile failed: "+g);u("falling back to ArrayBuffer instantiation");return c(b)})})})().catch(ca);return{}})();
216 | var le=f.___wasm_call_ctors=function(){return(le=f.___wasm_call_ctors=f.asm.Td).apply(null,arguments)},zb=f._free=function(){return(zb=f._free=f.asm.Ud).apply(null,arguments)},Ma=f._malloc=function(){return(Ma=f._malloc=f.asm.Vd).apply(null,arguments)},Fb=f.___errno_location=function(){return(Fb=f.___errno_location=f.asm.Wd).apply(null,arguments)},qc=f._memset=function(){return(qc=f._memset=f.asm.Xd).apply(null,arguments)};f._fflush=function(){return(f._fflush=f.asm.Yd).apply(null,arguments)};
217 | var vc=f._memalign=function(){return(vc=f._memalign=f.asm.Zd).apply(null,arguments)},Nc=f._ntohs=function(){return(Nc=f._ntohs=f.asm._d).apply(null,arguments)},Fc=f._htons=function(){return(Fc=f._htons=f.asm.$d).apply(null,arguments)},me=f._main=function(){return(me=f._main=f.asm.ae).apply(null,arguments)},Vd=f._emscripten_get_global_libc=function(){return(Vd=f._emscripten_get_global_libc=f.asm.be).apply(null,arguments)};
218 | f.___em_js__initPthreadsJS=function(){return(f.___em_js__initPthreadsJS=f.asm.ce).apply(null,arguments)};
219 | var Ud=f._htonl=function(){return(Ud=f._htonl=f.asm.de).apply(null,arguments)},Mb=f.__get_tzname=function(){return(Mb=f.__get_tzname=f.asm.ee).apply(null,arguments)},Lb=f.__get_daylight=function(){return(Lb=f.__get_daylight=f.asm.fe).apply(null,arguments)},Kb=f.__get_timezone=function(){return(Kb=f.__get_timezone=f.asm.ge).apply(null,arguments)},A=f.stackSave=function(){return(A=f.stackSave=f.asm.he).apply(null,arguments)},D=f.stackRestore=function(){return(D=f.stackRestore=f.asm.ie).apply(null,arguments)},
220 | Ha=f.stackAlloc=function(){return(Ha=f.stackAlloc=f.asm.je).apply(null,arguments)},Z=f._setThrew=function(){return(Z=f._setThrew=f.asm.ke).apply(null,arguments)};f._emscripten_main_browser_thread_id=function(){return(f._emscripten_main_browser_thread_id=f.asm.le).apply(null,arguments)};
221 | var yb=f.___pthread_tsd_run_dtors=function(){return(yb=f.___pthread_tsd_run_dtors=f.asm.me).apply(null,arguments)},Ab=f._emscripten_main_thread_process_queued_calls=function(){return(Ab=f._emscripten_main_thread_process_queued_calls=f.asm.ne).apply(null,arguments)};f._emscripten_current_thread_process_queued_calls=function(){return(f._emscripten_current_thread_process_queued_calls=f.asm.oe).apply(null,arguments)};
222 | var wb=f._emscripten_register_main_browser_thread_id=function(){return(wb=f._emscripten_register_main_browser_thread_id=f.asm.pe).apply(null,arguments)},lb=f._do_emscripten_dispatch_to_thread=function(){return(lb=f._do_emscripten_dispatch_to_thread=f.asm.qe).apply(null,arguments)};f._emscripten_async_run_in_main_thread=function(){return(f._emscripten_async_run_in_main_thread=f.asm.re).apply(null,arguments)};
223 | f._emscripten_sync_run_in_main_thread=function(){return(f._emscripten_sync_run_in_main_thread=f.asm.se).apply(null,arguments)};f._emscripten_sync_run_in_main_thread_0=function(){return(f._emscripten_sync_run_in_main_thread_0=f.asm.te).apply(null,arguments)};f._emscripten_sync_run_in_main_thread_1=function(){return(f._emscripten_sync_run_in_main_thread_1=f.asm.ue).apply(null,arguments)};
224 | f._emscripten_sync_run_in_main_thread_2=function(){return(f._emscripten_sync_run_in_main_thread_2=f.asm.ve).apply(null,arguments)};f._emscripten_sync_run_in_main_thread_xprintf_varargs=function(){return(f._emscripten_sync_run_in_main_thread_xprintf_varargs=f.asm.we).apply(null,arguments)};f._emscripten_sync_run_in_main_thread_3=function(){return(f._emscripten_sync_run_in_main_thread_3=f.asm.xe).apply(null,arguments)};
225 | var Ee=f._emscripten_sync_run_in_main_thread_4=function(){return(Ee=f._emscripten_sync_run_in_main_thread_4=f.asm.ye).apply(null,arguments)};f._emscripten_sync_run_in_main_thread_5=function(){return(f._emscripten_sync_run_in_main_thread_5=f.asm.ze).apply(null,arguments)};f._emscripten_sync_run_in_main_thread_6=function(){return(f._emscripten_sync_run_in_main_thread_6=f.asm.Ae).apply(null,arguments)};
226 | f._emscripten_sync_run_in_main_thread_7=function(){return(f._emscripten_sync_run_in_main_thread_7=f.asm.Be).apply(null,arguments)};var Ad=f._emscripten_run_in_main_runtime_thread_js=function(){return(Ad=f._emscripten_run_in_main_runtime_thread_js=f.asm.Ce).apply(null,arguments)},Gd=f.__emscripten_call_on_thread=function(){return(Gd=f.__emscripten_call_on_thread=f.asm.De).apply(null,arguments)};f._proxy_main=function(){return(f._proxy_main=f.asm.Ee).apply(null,arguments)};
227 | f._emscripten_tls_init=function(){return(f._emscripten_tls_init=f.asm.Fe).apply(null,arguments)};f.dynCall_ijiii=function(){return(f.dynCall_ijiii=f.asm.Ge).apply(null,arguments)};var Ge=f.dynCall_vijjjid=function(){return(Ge=f.dynCall_vijjjid=f.asm.He).apply(null,arguments)},He=f.dynCall_iiiijj=function(){return(He=f.dynCall_iiiijj=f.asm.Ie).apply(null,arguments)};f.dynCall_iiijiii=function(){return(f.dynCall_iiijiii=f.asm.Je).apply(null,arguments)};
228 | f.dynCall_jiiii=function(){return(f.dynCall_jiiii=f.asm.Ke).apply(null,arguments)};f.dynCall_jii=function(){return(f.dynCall_jii=f.asm.Le).apply(null,arguments)};var Ie=f.dynCall_iij=function(){return(Ie=f.dynCall_iij=f.asm.Me).apply(null,arguments)};f.dynCall_viiijj=function(){return(f.dynCall_viiijj=f.asm.Ne).apply(null,arguments)};f.dynCall_jij=function(){return(f.dynCall_jij=f.asm.Oe).apply(null,arguments)};f.dynCall_jiji=function(){return(f.dynCall_jiji=f.asm.Pe).apply(null,arguments)};
229 | f.dynCall_iiiji=function(){return(f.dynCall_iiiji=f.asm.Qe).apply(null,arguments)};f.dynCall_iiiiij=function(){return(f.dynCall_iiiiij=f.asm.Re).apply(null,arguments)};f.dynCall_jiiij=function(){return(f.dynCall_jiiij=f.asm.Se).apply(null,arguments)};f.dynCall_iiijjji=function(){return(f.dynCall_iiijjji=f.asm.Te).apply(null,arguments)};f.dynCall_iiiiiij=function(){return(f.dynCall_iiiiiij=f.asm.Ue).apply(null,arguments)};f.dynCall_jiiji=function(){return(f.dynCall_jiiji=f.asm.Ve).apply(null,arguments)};
230 | f.dynCall_viiiiijji=function(){return(f.dynCall_viiiiijji=f.asm.We).apply(null,arguments)};f.dynCall_viiiji=function(){return(f.dynCall_viiiji=f.asm.Xe).apply(null,arguments)};f.dynCall_viiiiji=function(){return(f.dynCall_viiiiji=f.asm.Ye).apply(null,arguments)};f.dynCall_jiiiii=function(){return(f.dynCall_jiiiii=f.asm.Ze).apply(null,arguments)};f.dynCall_jiii=function(){return(f.dynCall_jiii=f.asm._e).apply(null,arguments)};
231 | f.dynCall_jiiiiii=function(){return(f.dynCall_jiiiiii=f.asm.$e).apply(null,arguments)};f._ff_h264_cabac_tables=2115974;var xb=f._main_thread_futex=17195328;function pe(a,b,c){var d=A();try{return H.get(a)(b,c)}catch(e){D(d);if(e!==e+0&&"longjmp"!==e)throw e;Z(1,0)}}function we(a,b){var c=A();try{H.get(a)(b)}catch(d){D(c);if(d!==d+0&&"longjmp"!==d)throw d;Z(1,0)}}function ze(a,b,c,d,e){var g=A();try{H.get(a)(b,c,d,e)}catch(k){D(g);if(k!==k+0&&"longjmp"!==k)throw k;Z(1,0)}}
232 | function xe(a,b,c){var d=A();try{H.get(a)(b,c)}catch(e){D(d);if(e!==e+0&&"longjmp"!==e)throw e;Z(1,0)}}function oe(a,b){var c=A();try{return H.get(a)(b)}catch(d){D(c);if(d!==d+0&&"longjmp"!==d)throw d;Z(1,0)}}function re(a,b,c,d,e){var g=A();try{return H.get(a)(b,c,d,e)}catch(k){D(g);if(k!==k+0&&"longjmp"!==k)throw k;Z(1,0)}}function te(a,b,c,d,e,g,k,m,r){var q=A();try{return H.get(a)(b,c,d,e,g,k,m,r)}catch(t){D(q);if(t!==t+0&&"longjmp"!==t)throw t;Z(1,0)}}
233 | function ye(a,b,c,d){var e=A();try{H.get(a)(b,c,d)}catch(g){D(e);if(g!==g+0&&"longjmp"!==g)throw g;Z(1,0)}}function ne(a){var b=A();try{return H.get(a)()}catch(c){D(b);if(c!==c+0&&"longjmp"!==c)throw c;Z(1,0)}}function Ae(a,b,c,d,e,g){var k=A();try{H.get(a)(b,c,d,e,g)}catch(m){D(k);if(m!==m+0&&"longjmp"!==m)throw m;Z(1,0)}}function qe(a,b,c,d){var e=A();try{return H.get(a)(b,c,d)}catch(g){D(e);if(g!==g+0&&"longjmp"!==g)throw g;Z(1,0)}}
234 | function se(a,b,c,d,e,g){var k=A();try{return H.get(a)(b,c,d,e,g)}catch(m){D(k);if(m!==m+0&&"longjmp"!==m)throw m;Z(1,0)}}function Ce(a,b,c,d,e,g,k,m,r){var q=A();try{H.get(a)(b,c,d,e,g,k,m,r)}catch(t){D(q);if(t!==t+0&&"longjmp"!==t)throw t;Z(1,0)}}function Be(a,b,c,d,e,g,k){var m=A();try{H.get(a)(b,c,d,e,g,k)}catch(r){D(m);if(r!==r+0&&"longjmp"!==r)throw r;Z(1,0)}}function De(a,b,c,d,e,g,k,m,r,q){var t=A();try{Ge(a,b,c,d,e,g,k,m,r,q)}catch(w){D(t);if(w!==w+0&&"longjmp"!==w)throw w;Z(1,0)}}
235 | function ue(a,b,c,d,e,g,k,m){var r=A();try{return He(a,b,c,d,e,g,k,m)}catch(q){D(r);if(q!==q+0&&"longjmp"!==q)throw q;Z(1,0)}}function ve(a,b,c,d){var e=A();try{return Ie(a,b,c,d)}catch(g){D(e);if(g!==g+0&&"longjmp"!==g)throw g;Z(1,0)}}f.ccall=Ga;f.cwrap=function(a,b,c,d){c=c||[];var e=c.every(function(g){return"number"===g});return"string"!==b&&e&&!d?Fa(a):function(){return Ga(a,b,c,arguments,d)}};
236 | f.setValue=function(a,b,c){c=c||"i8";"*"===c.charAt(c.length-1)&&(c="i32");switch(c){case "i1":y[a>>0]=b;break;case "i8":y[a>>0]=b;break;case "i16":Qa[a>>1]=b;break;case "i32":E[a>>2]=b;break;case "i64":L=[b>>>0,(J=b,1<=+Math.abs(J)?0>>0:~~+Math.ceil((J-+(~~J>>>0))/4294967296)>>>0:0)];E[a>>2]=L[0];E[a+4>>2]=L[1];break;case "float":G[a>>2]=b;break;case "double":Sa[a>>3]=b;break;default:n("invalid type for setValue: "+c)}};f.writeAsciiToMemory=Pa;
237 | f.FS=O;f.PThread=M;f.PThread=M;f._pthread_self=Wd;f.wasmMemory=Ca;f.ExitStatus=wa;var Je;function wa(a){this.name="ExitStatus";this.message="Program terminated with exit("+a+")";this.status=a}cb=function Ke(){Je||Le();Je||(cb=Ke)};
238 | function Le(a){function b(){if(!Je&&(Je=!0,f.calledRun=!0,!Ea)){f.noFSInit||O.gg.Tg||O.gg();R.root=O.jf(R,{},null);nb(Wa);l||(O.Bh=!1,nb(Xa));ba(f);if(f.onRuntimeInitialized)f.onRuntimeInitialized();if(Me){var c=a;c=c||[];var d=c.length+1,e=Ha(4*(d+1));E[e>>2]=Na(ha);for(var g=1;g>2)+g]=Na(c[g-1]);E[(e>>2)+d]=0;f._proxy_main(d,e)}if(!l){if(f.postRun)for("function"==typeof f.postRun&&(f.postRun=[f.postRun]);f.postRun.length;)c=f.postRun.shift(),Za.unshift(c);nb(Za)}}}a=a||fa;if(!(0> /*C_STRUCTS.pthread.threadExitCode*/ 2, (ex instanceof Module["ExitStatus"]) ? ex.status : -2); /*A custom entry specific to Emscripten denoting that the thread crashed.*/
69 | Atomics.store(Module["HEAPU32"], (threadInfoStruct + 0) >> /*C_STRUCTS.pthread.threadStatus*/ 2, 1);
70 | Module["_emscripten_futex_wake"](threadInfoStruct + 0, /*C_STRUCTS.pthread.threadStatus*/ 2147483647);
71 | if (!(ex instanceof Module["ExitStatus"])) throw ex
72 | }
73 | }
74 | } else if (e.data.cmd === "cancel") {
75 | if (threadInfoStruct) {
76 | Module["PThread"].threadCancel()
77 | }
78 | } else if (e.data.target === "setimmediate") {} else if (e.data.cmd === "processThreadQueue") {
79 | if (threadInfoStruct) {
80 | Module["_emscripten_current_thread_process_queued_calls"]()
81 | }
82 | } else {
83 | err("worker.js received unknown command " + e.data.cmd);
84 | err(e.data)
85 | }
86 | } catch (ex) {
87 | err("worker.js onmessage() captured an uncaught exception: " + ex);
88 | if (ex && ex.stack) err(ex.stack);
89 | throw ex
90 | }
91 | };
92 | if (typeof process === "object" && typeof process.versions === "object" && typeof process.versions.node === "string") {
93 | self = {
94 | location: {
95 | href: __filename
96 | }
97 | };
98 | var onmessage = this.onmessage;
99 | var nodeWorkerThreads = require("worker_threads");
100 | global.Worker = nodeWorkerThreads.Worker;
101 | var parentPort = nodeWorkerThreads.parentPort;
102 | parentPort.on("message", function(data) {
103 | onmessage({
104 | data: data
105 | })
106 | });
107 | var nodeFS = require("fs");
108 | var nodeRead = function(filename) {
109 | return nodeFS.readFileSync(filename, "utf8")
110 | };
111 |
112 | function globalEval(x) {
113 | global.require = require;
114 | global.Module = Module;
115 | eval.call(null, x)
116 | }
117 | importScripts = function(f) {
118 | globalEval(nodeRead(f))
119 | };
120 | postMessage = function(msg) {
121 | parentPort.postMessage(msg)
122 | };
123 | if (typeof performance === "undefined") {
124 | performance = {
125 | now: function() {
126 | return Date.now()
127 | }
128 | }
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/v3/data/player/index.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --bg-color: #000;
3 | --color: #b2b8bd;
4 | --fill-color: #fff;
5 | --active-fill-color: #4487fb;
6 | --hover-color: #252623;
7 | }
8 | body {
9 | font-size: 13px;
10 | font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
11 | display: grid;
12 | grid-template-columns: 1fr min-content;
13 | margin: 0;
14 | height: 100vh;
15 | color: var(--color);
16 | background-color: var(--bg-color);
17 | fill: var(--fill-color);
18 | user-select: none;
19 | }
20 |
21 | @media screen and (max-width: 600px) {
22 | body[data-mode="expand"] {
23 | grid-template-columns: 1fr;
24 | grid-template-rows: 1fr minmax(146px, 40vh);
25 | }
26 | body[data-mode="expand"][data-type="audio"] {
27 | grid-template-rows: 66px 1fr !important;
28 | }
29 | }
30 | #video-container {
31 | display: flex;
32 | position: relative;
33 | overflow: hidden;
34 | }
35 |
36 | body[data-state="-1"] #video-container::before {
37 | content: 'Drag media files or double-click to browse';
38 | position: absolute;
39 | top: 0;
40 | left: 0;
41 | width: 100%;
42 | height: 100%;
43 | display: flex;
44 | align-items: center;
45 | justify-content: center;
46 | }
47 | video {
48 | flex: 1;
49 | width: 100%;
50 | height: 100%;
51 | outline: none;
52 | }
53 | body[data-state="-1"] video {
54 | display: none;
55 | }
56 |
57 | #toolbar {
58 | display: flex;
59 | flex-direction: column;
60 | overflow: hidden;
61 | padding: 5px;
62 | }
63 | body[data-mode=expand] #toolbar {
64 | width: 300px;
65 | }
66 |
67 | @media screen and (max-width: 600px) {
68 | body[data-mode=expand] #toolbar {
69 | width: unset;
70 | }
71 | }
72 | #toolbar input[type=checkbox] {
73 | display: none;
74 | }
75 | #buttons {
76 | display: flex;
77 | flex-direction: column;
78 | }
79 | body[data-mode=expand] #buttons {
80 | flex-direction: row;
81 | }
82 | #playlist {
83 | flex: 1;
84 | margin: 10px 0;
85 | overflow-x: hidden;
86 | overflow-y: auto;
87 | padding: 0;
88 | }
89 | #playlist li {
90 | white-space: nowrap;
91 | overflow: hidden;
92 | padding: 10px;
93 | display: flex;
94 | cursor: pointer;
95 | }
96 | #playlist li.active {
97 | background-color: var(--hover-color);
98 | }
99 | #playlist li span[data-id=name] {
100 | overflow: hidden;
101 | text-overflow: ellipsis;
102 | display: block;
103 | margin-right: 10px;
104 | flex: 1;
105 | }
106 | body:not([data-mode=expand]) #playlist li {
107 | display: none;
108 | }
109 | label {
110 | margin: 5px;
111 | border-radius: 50%;
112 | cursor: pointer;
113 | display: flex;
114 | }
115 | svg {
116 | padding: 5px;
117 | position: relative;
118 | }
119 | label * {
120 | pointer-events: none;
121 | }
122 | label:hover,
123 | input[type=checkbox]:checked + label {
124 | background-color: var(--hover-color);
125 | }
126 |
127 | .disabled {
128 | opacity: 0.3;
129 | pointer-events: none;
130 | }
131 | input[type=checkbox]:checked + label > svg {
132 | fill: var(--active-fill-color);
133 | }
134 | #shuffle {
135 | display: none;
136 | }
137 | #shuffle svg {
138 | padding: 9px;
139 | }
140 | #next svg,
141 | #previous svg {
142 | padding: 8px;
143 | }
144 | #repeat[data-mode="no-repeat"] {
145 | fill: #565656;
146 | }
147 | #repeat[data-mode="repeat-all"] path:first-child,
148 | #repeat[data-mode="no-repeat"] path:first-child {
149 | display: none;
150 | }
151 | #repeat[data-mode="repeat-one"] path:last-child {
152 | display: none;
153 | }
154 | #speed[data-mode="1x"] text:not(:nth-child(1)) {
155 | display: none;
156 | }
157 | #speed[data-mode="2x"] text:not(:nth-child(2)) {
158 | display: none;
159 | }
160 | #speed[data-mode="4x"] text:not(:nth-child(3)) {
161 | display: none;
162 | }
163 | #speed[data-mode="8x"] text:not(:nth-child(4)) {
164 | display: none;
165 | }
166 | #boost[data-mode="1b"] text:not(:nth-child(1)) {
167 | display: none;
168 | }
169 | #boost[data-mode="2b"] text:not(:nth-child(2)) {
170 | display: none;
171 | }
172 | #boost[data-mode="4b"] text:not(:nth-child(3)) {
173 | display: none;
174 | }
175 |
176 | .hidden {
177 | display: none;
178 | }
179 |
--------------------------------------------------------------------------------
/v3/data/player/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Video Player
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
78 |
79 |
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/v3/data/player/index.mjs:
--------------------------------------------------------------------------------
1 | import playlist from './playlist.mjs';
2 | import drag from './drag.mjs';
3 | import './context.mjs';
4 | import './boost.mjs';
5 | import './capture.mjs';
6 | import './storage.mjs';
7 | import './keyboard.mjs';
8 |
9 | const args = new URLSearchParams(location.search);
10 |
11 | drag.onDrag(files => playlist.loadVideo(files));
12 |
13 | if (args.has('src')) {
14 | playlist.loadVideo([{
15 | src: args.get('src')
16 | }]);
17 | }
18 |
19 | chrome.runtime.onMessage.addListener((request, sender, response) => {
20 | if (request.method === 'open') {
21 | response(true);
22 | chrome.runtime.sendMessage({
23 | method: 'focus'
24 | });
25 |
26 | if (request.src) {
27 | playlist.loadVideo([{
28 | src: request.src
29 | }]);
30 | }
31 | }
32 | });
33 |
34 | // show/hide buttons
35 | {
36 | const update = () => chrome.storage.local.get({
37 | 'add-button': true,
38 | 'repeat-button': true,
39 | 'capture-button': true,
40 | 'speed-button': true,
41 | 'boost-button': true
42 | }, prefs => {
43 | for (const [name, value] of Object.entries(prefs)) {
44 | document.getElementById(name.replace('-button', '')).classList[value ? 'remove' : 'add']('hidden');
45 | }
46 | });
47 | update();
48 | chrome.storage.onChanged.addListener(ps => {
49 | if (ps['add-button'] || ps['repeat-button'] || ps['capture-button'] || ps['speed-button'] || ps['boost-button']) {
50 | update();
51 | }
52 | });
53 | }
54 |
--------------------------------------------------------------------------------
/v3/data/player/keyboard.mjs:
--------------------------------------------------------------------------------
1 | import notify from './notify.mjs';
2 |
3 | const keyboard = {};
4 | const v = document.querySelector('video');
5 |
6 | const rules = [{
7 | condition(meta, code, shift) {
8 | return code === 'KeyP' && meta && shift;
9 | },
10 | action() {
11 | document.getElementById('previous').click();
12 |
13 | return true;
14 | }
15 | }, {
16 | condition(meta, code, shift) {
17 | return code === 'KeyN' && meta && shift;
18 | },
19 | action() {
20 | document.getElementById('next').click();
21 |
22 | return true;
23 | }
24 | }, { // toggle playlist
25 | condition(meta, code) {
26 | return code === 'KeyP' && meta;
27 | },
28 | action() {
29 | document.getElementById('p-button').click();
30 |
31 | return true;
32 | }
33 | }, { // change volume
34 | condition(meta, code) {
35 | if (code === 'ArrowUp' || code === 'ArrowDown') {
36 | return true;
37 | }
38 | },
39 | action(e) {
40 | const volume = Math.min(1, Math.max(0, Math.round(v.volume * 100 + (e.code === 'ArrowUp' ? 5 : -5)) / 100));
41 | try {
42 | v.volume = volume;
43 | }
44 | catch (e) {
45 | console.log(volume, e);
46 | }
47 | notify.display('Volume: ' + (v.volume * 100).toFixed(0) + '%');
48 | return true;
49 | }
50 | }, {
51 | condition(meta, code) {
52 | return code === 'KeyB' && meta;
53 | },
54 | action() {
55 | document.getElementById('boost').click();
56 |
57 | return true;
58 | }
59 | }, {
60 | condition(meta, code) {
61 | return code === 'KeyS' && meta;
62 | },
63 | action() {
64 | document.getElementById('capture').click();
65 |
66 | return true;
67 | }
68 | }, {
69 | condition(meta, code) {
70 | return code === 'KeyX' && meta;
71 | },
72 | action() {
73 | document.getElementById('speed').click();
74 |
75 | return true;
76 | }
77 | }];
78 |
79 | document.addEventListener('keydown', e => {
80 | const meta = e.metaKey || e.ctrlKey;
81 | for (const {condition, action} of rules) {
82 | if (condition(meta, e.code, e.shiftKey)) {
83 | if (action(e)) {
84 | e.preventDefault();
85 | }
86 | break;
87 | }
88 | }
89 | });
90 |
91 | keyboard.register = rule => rules.push(rule);
92 |
93 | export default keyboard;
94 |
--------------------------------------------------------------------------------
/v3/data/player/notify.mjs:
--------------------------------------------------------------------------------
1 | const notify = {};
2 |
3 | const toast = document.createElement('div');
4 | toast.style = `
5 | position: fixed;
6 | top: 10px;
7 | right: 10px;
8 | `;
9 | document.body.appendChild(toast);
10 |
11 | let id;
12 | notify.display = (msg, period = 750) => {
13 | toast.textContent = msg;
14 | clearTimeout(id);
15 | id = setTimeout(() => toast.textContent = '', period);
16 | };
17 | notify.clear = () => {
18 | toast.textContent = '';
19 | clearTimeout(id);
20 | };
21 |
22 | export default notify;
23 |
--------------------------------------------------------------------------------
/v3/data/player/playlist.mjs:
--------------------------------------------------------------------------------
1 | /* global MediaMetadata, muxjs */
2 | import notify from './notify.mjs';
3 | import stream from './stream.mjs';
4 |
5 | const root = document.getElementById('playlist');
6 | const video = document.querySelector('video');
7 | const next = document.getElementById('next');
8 | const previous = document.getElementById('previous');
9 | const repeat = document.getElementById('repeat');
10 | const speed = document.getElementById('speed');
11 | const boost = document.getElementById('boost');
12 | const capture = document.getElementById('capture');
13 |
14 | video.addEventListener('blur', () => video.focus());
15 | video.addEventListener('canplay', () => {
16 | try {
17 | document.body.dataset.type = video.captureStream().getTracks().some(t => t.kind === 'video') ? 'video' : 'audio';
18 | }
19 | catch (e) {
20 | console.log(e);
21 | }
22 | });
23 |
24 | const scrollIntoView = e => {
25 | const rect = e.getBoundingClientRect();
26 |
27 | if (rect.top < 0 || rect.bottom > root.clientHeight) {
28 | e.scrollIntoView();
29 | }
30 | };
31 |
32 | const stats = new WeakMap();
33 | let delayId;
34 |
35 | let state = -1; // current playing state
36 | const playlist = {
37 | PlayerState: {
38 | 'UNSTARTED': -1,
39 | 'ENDED': 0,
40 | 'PLAYING': 1,
41 | 'PAUSED': 2,
42 | 'BUFFERING': 3,
43 | 'CUED': 5
44 | },
45 | configs: {
46 | delay: 1500
47 | },
48 | entries: [],
49 | index: -1, // current playlist index
50 | get state() {
51 | return state;
52 | },
53 | set state(s) {
54 | state = s;
55 | document.body.dataset.state = s;
56 | for (const c of playlist.onStateChange.cs) {
57 | c(s);
58 | }
59 | },
60 | open() {
61 | document.body.dataset.mode = 'expand';
62 | const active = document.querySelector('li.active');
63 | if (active) {
64 | scrollIntoView(active);
65 | }
66 | },
67 | close() {
68 | document.body.dataset.mode = 'collapse';
69 | },
70 | play(index = playlist.index, delay = 0) {
71 | clearTimeout(delayId);
72 | if (delay) {
73 | delayId = setTimeout(() => playlist.play(index, 0), delay);
74 | return;
75 | }
76 | if (video.src) {
77 | URL.revokeObjectURL(video.src);
78 | }
79 |
80 | playlist.index = index === -1 ? 0 : (index % playlist.entries.length);
81 | if (playlist.index + 1 === playlist.entries.length) {
82 | next.classList.add('disabled');
83 | navigator.mediaSession.setActionHandler('nexttrack', null);
84 | }
85 | else {
86 | next.classList.remove('disabled');
87 | navigator.mediaSession.setActionHandler('nexttrack', () => next.click());
88 | }
89 | if (playlist.index === 0) {
90 | previous.classList.add('disabled');
91 | navigator.mediaSession.setActionHandler('previoustrack', null);
92 | }
93 | else {
94 | previous.classList.remove('disabled');
95 | navigator.mediaSession.setActionHandler('previoustrack', () => previous.click());
96 | }
97 | if (playlist.entries.length) {
98 | document.getElementById('p-button-view').classList.remove('disabled');
99 | document.getElementById('shuffle').classList.remove('disabled');
100 | }
101 | else {
102 | document.getElementById('shuffle').classList.add('disabled');
103 | }
104 |
105 | navigator.mediaSession.setActionHandler('seekbackward', () => {
106 | video.currentTime -= 10;
107 | });
108 | navigator.mediaSession.setActionHandler('seekforward', () => {
109 | video.currentTime += 10;
110 | });
111 |
112 | const s = playlist.entries[playlist.index];
113 |
114 | if (s.name) {
115 | const u = URL.createObjectURL(s);
116 | video.src = u;
117 | }
118 | else {
119 | video.src = s.src;
120 | }
121 | video.playbackRate = parseInt(speed.dataset.mode);
122 | document.title = (s.name || s.src) + ' :: ' + chrome.runtime.getManifest().name;
123 | navigator.mediaSession.metadata = new MediaMetadata({
124 | title: document.title
125 | });
126 |
127 | // active entry
128 | for (const e of [...document.querySelectorAll('li.active')]) {
129 | e.classList.remove('active');
130 | }
131 | s.e.classList.add('active');
132 | scrollIntoView(s.e);
133 | const currentTime = stats.get(s);
134 | if (currentTime !== undefined) {
135 | video.currentTime = currentTime;
136 | }
137 | video.origin = s;
138 | video.play().catch(e => {
139 | const src = video.src;
140 | notify.display(e.message + ' Fallback Decoding...', 10000);
141 |
142 |
143 | const mediaSource = new MediaSource();
144 | video.src = URL.createObjectURL(mediaSource);
145 | video.play().catch(e => notify.display(e.message, 10000));
146 |
147 | let sourceBuf;
148 | let done = false;
149 | const push = buffer => {
150 | if (push.once !== true) {
151 | notify.clear();
152 | // create a buffer using the correct mime type
153 | const tracks = muxjs.mp4.probe.tracks(buffer);
154 |
155 | const mime = `video/mp4; codecs="${tracks.map(t => t.codec).join(',')}"`;
156 | sourceBuf = mediaSource.addSourceBuffer(mime);
157 | sourceBuf.addEventListener('updateend', () => {
158 | if (done) {
159 | mediaSource.endOfStream();
160 | }
161 | });
162 |
163 | mediaSource.duration = 5;
164 | sourceBuf.timestampOffset = 0;
165 | sourceBuf.appendBuffer(buffer);
166 | push.once = true;
167 | }
168 | else {
169 | mediaSource.duration += 5;
170 | sourceBuf.timestampOffset += 5;
171 | sourceBuf.appendBuffer(buffer.buffer);
172 | }
173 | };
174 |
175 | stream(src, push, () => done = true);
176 | });
177 | window.setTimeout(() => video.focus(), 100);
178 | },
179 | stopVideo() {
180 | video.pause();
181 | video.currentTime = 0;
182 | },
183 | loadVideo(files) {
184 | const index = playlist.entries.length;
185 | playlist.cueVideo(files);
186 | this.play(index);
187 | },
188 | cueVideo(files) {
189 | playlist.entries.push(...files);
190 | const f = document.createDocumentFragment();
191 | for (const file of files) {
192 | const li = document.createElement('li');
193 | const name = document.createElement('span');
194 | name.dataset.id = 'name';
195 | name.textContent = file.name || file.src;
196 | const duration = document.createElement('span');
197 | duration.dataset.id = 'duration';
198 | duration.textContent = '--:--';
199 | li.appendChild(name);
200 | li.appendChild(duration);
201 | file.e = li;
202 | li.file = file;
203 | f.appendChild(li);
204 | }
205 | root.appendChild(f);
206 | },
207 | onStateChange(c) {
208 | playlist.onStateChange.cs.push(c);
209 | }
210 | };
211 | playlist.onStateChange.cs = [];
212 |
213 | video.addEventListener('timeupdate', () => {
214 | stats.set(video.origin, video.currentTime);
215 | });
216 | video.addEventListener('abort', () => playlist.state = 0);
217 | video.addEventListener('error', () => playlist.state = 0);
218 | video.addEventListener('emptied', () => playlist.state = 0);
219 | video.addEventListener('ended', () => {
220 | stats.set(video.origin, 0);
221 | playlist.state = 0;
222 | navigator.mediaSession.setActionHandler('seekbackward', null);
223 | navigator.mediaSession.setActionHandler('seekforward', null);
224 |
225 | capture.classList.add('disabled');
226 |
227 | if (playlist.index + 1 !== playlist.entries.length) {
228 | playlist.play(playlist.index + 1, playlist.configs.delay);
229 | }
230 | else {
231 | if (repeat.dataset.mode === 'repeat-all') {
232 | playlist.play(0, playlist.configs.delay);
233 | }
234 | else if (repeat.dataset.mode === 'repeat-one') {
235 | playlist.play(playlist.index, playlist.configs.delay);
236 | }
237 | }
238 | });
239 | video.addEventListener('play', () => {
240 | playlist.state = 1;
241 | capture.classList.remove('disabled');
242 | });
243 | video.addEventListener('playing', () => playlist.state = 1);
244 | video.addEventListener('pause', () => playlist.state = 2);
245 | video.addEventListener('waiting', () => playlist.state = 3);
246 | video.addEventListener('loadstart', () => playlist.state = 3);
247 | video.addEventListener('loadedmetadata', () => {
248 | const d = video.duration;
249 | const h = Math.floor(d / 3600);
250 | const m = Math.floor(d % 3600 / 60);
251 | const s = Math.floor(d % 3600 % 60);
252 |
253 | video.origin.e.querySelector('span[data-id=duration]').textContent =
254 | ('0' + h).substr(-2) + ':' + ('0' + m).substr(-2) + ':' + ('0' + s).substr(-2);
255 | });
256 |
257 | document.getElementById('p-button').addEventListener('change', e => {
258 | playlist[e.target.checked ? 'open' : 'close']();
259 | });
260 |
261 | root.addEventListener('click', e => {
262 | const li = e.target.closest('li');
263 | if (li) {
264 | const index = playlist.entries.indexOf(li.file);
265 | if (index !== playlist.index) {
266 | playlist.play(index);
267 | }
268 | }
269 | });
270 | previous.addEventListener('click', () => playlist.play(playlist.index - 1));
271 | next.addEventListener('click', () => playlist.play(playlist.index + 1));
272 |
273 | repeat.addEventListener('click', e => {
274 | const modes = ['no-repeat', 'repeat-all', 'repeat-one'];
275 | const index = (modes.indexOf(e.target.dataset.mode) + 1) % 3;
276 | repeat.dataset.mode = modes[index];
277 | });
278 | speed.addEventListener('click', e => {
279 | const modes = ['1x', '2x', '4x', '8x'];
280 | const index = (modes.indexOf(e.target.dataset.mode) + 1) % 4;
281 | speed.dataset.mode = modes[index];
282 | video.playbackRate = parseInt(modes[index]);
283 | });
284 | boost.addEventListener('click', e => {
285 | const modes = ['1b', '2b', '4b'];
286 | const index = (modes.indexOf(e.target.dataset.mode) + 1) % 3;
287 | boost.dataset.mode = modes[index];
288 | setTimeout(() => {
289 | video.boost = parseInt(modes[index]);
290 | }, 100);
291 | });
292 | capture.addEventListener('click', e => {
293 | video.capture();
294 | });
295 |
296 | export default playlist;
297 |
--------------------------------------------------------------------------------
/v3/data/player/poster.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/v3/data/player/storage.mjs:
--------------------------------------------------------------------------------
1 | import notify from './notify.mjs';
2 | const storage = {};
3 |
4 | storage.set = prefs => new Promise(resolve => chrome.storage.local.set(prefs, resolve));
5 | storage.get = prefs => new Promise(resolve => chrome.storage.local.get(prefs, resolve));
6 |
7 | const video = document.querySelector('video');
8 |
9 | // persist
10 | storage.get({
11 | 'repeat': 'no-repeat',
12 | 'volume': 0.8,
13 | 'speed': 1
14 | }).then(prefs => {
15 | document.getElementById('repeat').dataset.mode = prefs.repeat;
16 | document.getElementById('speed').dataset.mode = prefs.speed + 'x';
17 | video.volume = prefs.volume;
18 | video.playbackRate = prefs.speed;
19 | });
20 | document.getElementById('repeat').addEventListener('click', e => chrome.storage.local.set({
21 | 'repeat': e.target.dataset.mode
22 | }));
23 | document.getElementById('speed').addEventListener('click', e => chrome.storage.local.set({
24 | 'speed': parseInt(e.target.dataset.mode)
25 | }, () => notify.display('Speed: ' + e.target.dataset.mode.toUpperCase())));
26 | video.addEventListener('volumechange', e => {
27 | chrome.storage.local.set({
28 | 'volume': e.target.volume
29 | });
30 | });
31 | video.addEventListener('boostchange', () => {
32 | notify.display('Boost: ' + video.boost + 'B');
33 | });
34 |
35 |
36 | export default storage;
37 |
--------------------------------------------------------------------------------
/v3/data/player/stream.mjs:
--------------------------------------------------------------------------------
1 | /* global createFFmpegCore */
2 |
3 | const parseArgs = (Core, args) => {
4 | const argsPtr = Core._malloc(args.length * Uint32Array.BYTES_PER_ELEMENT);
5 | args.forEach((s, idx) => {
6 | const buf = Core._malloc(s.length + 1);
7 | Core.writeAsciiToMemory(s, buf);
8 | Core.setValue(argsPtr + (Uint32Array.BYTES_PER_ELEMENT * idx), buf, 'i32');
9 | });
10 | return [args.length, argsPtr];
11 | };
12 |
13 | const stream = async (src, segment = () => {}, flush = () => {}) => {
14 | let index = 0;
15 | let done = false;
16 | const Core = await createFFmpegCore({
17 | mainScriptUrlOrBlob: 'ffmpeg-core.js',
18 | printErr(msg) {
19 | console.info(msg);
20 | },
21 | print(msg) {
22 | if (msg === 'FFMPEG_END') {
23 | done = true;
24 | }
25 | },
26 | locateFile(path, prefix) {
27 | return prefix + path;
28 | }
29 | });
30 |
31 | const name = src.split('/').pop() || 'unknown';
32 |
33 | const buffer = await fetch(src).then(r => r.arrayBuffer());
34 | Core.FS.writeFile(name, new Uint8Array(buffer));
35 |
36 |
37 | const args = [
38 | './ffmpeg',
39 | '-nostdin',
40 | '-y',
41 | '-i',
42 | name,
43 | '-g',
44 | '1',
45 | // Encode for MediaStream
46 | '-segment_format_options',
47 | 'movflags=frag_keyframe+empty_moov+default_base_moof',
48 | // encode 5 second segments
49 | '-segment_time',
50 | '5',
51 | // write to files by index
52 | '-f',
53 | 'segment',
54 | '%d.mp4'
55 | ];
56 | Core.cwrap('proxy_main', 'number', ['number', 'number'])(...parseArgs(Core, args));
57 |
58 | const check = () => {
59 | for (;;) {
60 | const name = [`${index}.mp4`, `${index + 1}.mp4`];
61 | if (
62 | (Core.FS.readdir('/').includes(name[0]) && done) ||
63 | (Core.FS.readdir('/').includes(name[0]) && Core.FS.readdir('/').includes(name[1]))
64 | ) {
65 | index += 1;
66 |
67 | segment(Core.FS.readFile(name[0]));
68 | Core.FS.unlink(name[0]);
69 | }
70 | else {
71 | if (done) {
72 | clearInterval(id);
73 | flush();
74 | }
75 | break;
76 | }
77 | }
78 | };
79 |
80 | const id = setInterval(check, 100);
81 | };
82 |
83 | export default stream;
84 |
--------------------------------------------------------------------------------
/v3/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 3,
3 | "version": "0.2.4",
4 | "name": "Video Player",
5 | "description": "__MSG_description__",
6 | "default_locale": "en",
7 | "permissions": [
8 | "storage",
9 | "contextMenus"
10 | ],
11 | "offline_enabled": true,
12 | "icons": {
13 | "16": "data/icons/16.png",
14 | "19": "data/icons/19.png",
15 | "32": "data/icons/32.png",
16 | "38": "data/icons/38.png",
17 | "48": "data/icons/48.png",
18 | "64": "data/icons/64.png",
19 | "128": "data/icons/128.png",
20 | "256": "data/icons/256.png",
21 | "512": "data/icons/512.png"
22 | },
23 | "background": {
24 | "service_worker": "worker.js"
25 | },
26 | "action": {},
27 | "content_security_policy": {
28 | "extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'"
29 | },
30 | "homepage_url": "https://add0n.com/video-player.html",
31 | "commands": {
32 | "_execute_action": {
33 | "description": "Press the action button"
34 | }
35 | },
36 | "file_handlers": [{
37 | "action": "/data/player/index.html",
38 | "name": "Open with MediaPlayer",
39 | "accept": {
40 | "video/avi": [".avi"],
41 | "video/mp4": [".mp4"],
42 | "video/webm": [".webm"],
43 | "video/x-flv": [".flv"],
44 | "video/quicktime": [".mov"],
45 | "video/ogg": [".ogv"],
46 | "video/3gpp": [".3gp"],
47 | "video/mpeg": [".mpg"],
48 | "video/x-ms-wmv": [".wmv"],
49 | "video/x-matroska": [".mkv"],
50 | "video/dvd": [".vob"]
51 | },
52 | "launch_type": "single-client"
53 | }]
54 | }
55 |
--------------------------------------------------------------------------------
/v3/worker.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | self.importScripts('context.js');
4 |
5 | const storage = {
6 | get(prefs) {
7 | return new Promise(resolve => chrome.storage.local.get(prefs, resolve));
8 | }
9 | };
10 |
11 | const player = {
12 | async open(args) {
13 | const prefs = await storage.get({
14 | width: 800,
15 | height: 500,
16 | top: -1,
17 | left: -1
18 | });
19 |
20 | const win = await chrome.windows.getCurrent();
21 | const left = prefs.left < 0 ? win.left + Math.round((win.width - prefs.width) / 2) : prefs.left;
22 | const top = prefs.top < 0 ? win.top + Math.round((win.height - prefs.height) / 2) : prefs.top;
23 |
24 | let url = 'data/player/index.html';
25 | if (args) {
26 | url += '?' + Object.entries(args).map(([key, value]) => key + '=' + encodeURIComponent(value)).join('&');
27 | }
28 |
29 | chrome.windows.create({
30 | url,
31 | type: 'popup',
32 | width: prefs.width,
33 | height: prefs.height,
34 | left,
35 | top
36 | });
37 | }
38 | };
39 |
40 | chrome.action.onClicked.addListener(() => {
41 | chrome.runtime.sendMessage({
42 | method: 'open'
43 | }, r => {
44 | chrome.runtime.lastError;
45 | if (r !== true) {
46 | player.open();
47 | }
48 | });
49 | });
50 |
51 | chrome.runtime.onMessage.addListener((request, sender) => {
52 | if (request.method === 'focus') {
53 | chrome.windows.update(sender.tab.windowId, {
54 | focused: true
55 | });
56 | }
57 | });
58 |
59 | /* FAQs & Feedback */
60 | {
61 | const {management, runtime: {onInstalled, setUninstallURL, getManifest}, storage, tabs} = chrome;
62 | if (navigator.webdriver !== true) {
63 | const page = getManifest().homepage_url;
64 | const {name, version} = getManifest();
65 | onInstalled.addListener(({reason, previousVersion}) => {
66 | management.getSelf(({installType}) => installType === 'normal' && storage.local.get({
67 | 'faqs': true,
68 | 'last-update': 0
69 | }, prefs => {
70 | if (reason === 'install' || (prefs.faqs && reason === 'update')) {
71 | const doUpdate = (Date.now() - prefs['last-update']) / 1000 / 60 / 60 / 24 > 45;
72 | if (doUpdate && previousVersion !== version) {
73 | tabs.query({active: true, currentWindow: true}, tbs => tabs.create({
74 | url: page + '?version=' + version + (previousVersion ? '&p=' + previousVersion : '') + '&type=' + reason,
75 | active: reason === 'install',
76 | ...(tbs && tbs.length && {index: tbs[0].index + 1})
77 | }));
78 | storage.local.set({'last-update': Date.now()});
79 | }
80 | }
81 | }));
82 | });
83 | setUninstallURL(page + '?rd=feedback&name=' + encodeURIComponent(name) + '&version=' + version);
84 | }
85 | }
86 |
--------------------------------------------------------------------------------