@brandbrigade/ott-bb-player 1.0.16 → 1.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/js/OTTPlayer.js +4335 -0
- package/js/OTTPlayer.prod.js +1 -1
- package/{sample.html → mediakind.html} +9 -9
- package/package.json +1 -1
- package/js/lib/uzip.js +0 -1
- package/js/lib/webgl-utils.js +0 -1
- package/js/worker/MetaDataDownloaderThread.js +0 -1
- package/js/worker/OverlayThread.js +0 -1
- package/sample_BB_stream.html +0 -188
- package/wmc.prod.js +0 -1
- package/wmc_new.prod.js +0 -1
package/js/OTTPlayer.js
ADDED
|
@@ -0,0 +1,4335 @@
|
|
|
1
|
+
// test v3
|
|
2
|
+
const META_WORKER = `try {
|
|
3
|
+
|
|
4
|
+
let UZIP = {};
|
|
5
|
+
if(typeof module == "object") module.exports = UZIP;
|
|
6
|
+
UZIP["parse"] = function(buf, onlyNames) // ArrayBuffer
|
|
7
|
+
{
|
|
8
|
+
let rUs = UZIP.bin.readUshort, rUi = UZIP.bin.readUint, o = 0, out = {};
|
|
9
|
+
let data = new Uint8Array(buf);
|
|
10
|
+
let eocd = data.length-4;
|
|
11
|
+
|
|
12
|
+
while(rUi(data, eocd)!=0x06054b50) eocd--;
|
|
13
|
+
|
|
14
|
+
o = eocd;
|
|
15
|
+
o+=4; // sign = 0x06054b50
|
|
16
|
+
o+=4; // disks = 0;
|
|
17
|
+
let cnu = rUs(data, o); o+=2;
|
|
18
|
+
let cnt = rUs(data, o); o+=2;
|
|
19
|
+
|
|
20
|
+
let csize = rUi(data, o); o+=4;
|
|
21
|
+
let coffs = rUi(data, o); o+=4;
|
|
22
|
+
|
|
23
|
+
o = coffs;
|
|
24
|
+
for(let i=0; i<cnu; i++)
|
|
25
|
+
{
|
|
26
|
+
let sign = rUi(data, o); o+=4;
|
|
27
|
+
o += 4; // versions;
|
|
28
|
+
o += 4; // flag + compr
|
|
29
|
+
o += 4; // time
|
|
30
|
+
|
|
31
|
+
let crc32 = rUi(data, o); o+=4;
|
|
32
|
+
let csize = rUi(data, o); o+=4;
|
|
33
|
+
let usize = rUi(data, o); o+=4;
|
|
34
|
+
|
|
35
|
+
let nl = rUs(data, o), el = rUs(data, o+2), cl = rUs(data, o+4); o += 6; // name, extra, comment
|
|
36
|
+
o += 8; // disk, attribs
|
|
37
|
+
|
|
38
|
+
let roff = rUi(data, o); o+=4;
|
|
39
|
+
o += nl + el + cl;
|
|
40
|
+
|
|
41
|
+
UZIP._readLocal(data, roff, out, csize, usize, onlyNames);
|
|
42
|
+
}
|
|
43
|
+
//console.log(out);
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
UZIP._readLocal = function(data, o, out, csize, usize, onlyNames)
|
|
48
|
+
{
|
|
49
|
+
let rUs = UZIP.bin.readUshort, rUi = UZIP.bin.readUint;
|
|
50
|
+
let sign = rUi(data, o); o+=4;
|
|
51
|
+
let ver = rUs(data, o); o+=2;
|
|
52
|
+
let gpflg = rUs(data, o); o+=2;
|
|
53
|
+
//if((gpflg&8)!=0) throw "unknown sizes";
|
|
54
|
+
let cmpr = rUs(data, o); o+=2;
|
|
55
|
+
|
|
56
|
+
let time = rUi(data, o); o+=4;
|
|
57
|
+
|
|
58
|
+
let crc32 = rUi(data, o); o+=4;
|
|
59
|
+
//let csize = rUi(data, o); o+=4;
|
|
60
|
+
//let usize = rUi(data, o); o+=4;
|
|
61
|
+
o+=8;
|
|
62
|
+
|
|
63
|
+
let nlen = rUs(data, o); o+=2;
|
|
64
|
+
let elen = rUs(data, o); o+=2;
|
|
65
|
+
|
|
66
|
+
let name = UZIP.bin.readUTF8(data, o, nlen); o+=nlen; //console.log(name);
|
|
67
|
+
o += elen;
|
|
68
|
+
|
|
69
|
+
//console.log(sign.toString(16), ver, gpflg, cmpr, crc32.toString(16), "csize, usize", csize, usize, nlen, elen, name, o);
|
|
70
|
+
if(onlyNames) { out[name]={size:usize, csize:csize}; return; }
|
|
71
|
+
let file = new Uint8Array(data.buffer, o);
|
|
72
|
+
if(false) {}
|
|
73
|
+
else if(cmpr==0) out[name] = new Uint8Array(file.buffer.slice(o, o+csize));
|
|
74
|
+
else if(cmpr==8) {
|
|
75
|
+
let buf = new Uint8Array(usize); UZIP.inflateRaw(file, buf);
|
|
76
|
+
/*let nbuf = pako["inflateRaw"](file);
|
|
77
|
+
if(usize>8514000) {
|
|
78
|
+
//console.log(PUtils.readASCII(buf , 8514500, 500));
|
|
79
|
+
//console.log(PUtils.readASCII(nbuf, 8514500, 500));
|
|
80
|
+
}
|
|
81
|
+
for(let i=0; i<buf.length; i++) if(buf[i]!=nbuf[i]) { console.log(buf.length, nbuf.length, usize, i); throw "e"; }
|
|
82
|
+
*/
|
|
83
|
+
out[name] = buf;
|
|
84
|
+
}
|
|
85
|
+
else throw "unknown compression method: "+cmpr;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
UZIP.inflateRaw = function(file, buf) { return UZIP.F.inflate(file, buf); }
|
|
89
|
+
UZIP.inflate = function(file, buf) {
|
|
90
|
+
let CMF = file[0], FLG = file[1];
|
|
91
|
+
let CM = (CMF&15), CINFO = (CMF>>>4);
|
|
92
|
+
//console.log(CM, CINFO,CMF,FLG);
|
|
93
|
+
return UZIP.inflateRaw(new Uint8Array(file.buffer, file.byteOffset+2, file.length-6), buf);
|
|
94
|
+
}
|
|
95
|
+
UZIP.deflate = function(data, opts/*, buf, off*/) {
|
|
96
|
+
if(opts==null) opts={level:6};
|
|
97
|
+
let off=0, buf=new Uint8Array(50+Math.floor(data.length*1.1));
|
|
98
|
+
buf[off]=120; buf[off+1]=156; off+=2;
|
|
99
|
+
off = UZIP.F.deflateRaw(data, buf, off, opts.level);
|
|
100
|
+
let crc = UZIP.adler(data, 0, data.length);
|
|
101
|
+
buf[off+0]=((crc>>>24)&255);
|
|
102
|
+
buf[off+1]=((crc>>>16)&255);
|
|
103
|
+
buf[off+2]=((crc>>> 8)&255);
|
|
104
|
+
buf[off+3]=((crc>>> 0)&255);
|
|
105
|
+
return new Uint8Array(buf.buffer, 0, off+4);
|
|
106
|
+
}
|
|
107
|
+
UZIP.deflateRaw = function(data, opts) {
|
|
108
|
+
if(opts==null) opts={level:6};
|
|
109
|
+
let buf=new Uint8Array(50+Math.floor(data.length*1.1));
|
|
110
|
+
let off = UZIP.F.deflateRaw(data, buf, off, opts.level);
|
|
111
|
+
return new Uint8Array(buf.buffer, 0, off);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
UZIP.encode = function(obj, noCmpr) {
|
|
116
|
+
if(noCmpr==null) noCmpr=false;
|
|
117
|
+
let tot = 0, wUi = UZIP.bin.writeUint, wUs = UZIP.bin.writeUshort;
|
|
118
|
+
let zpd = {};
|
|
119
|
+
for(let p in obj) { let cpr = !UZIP._noNeed(p) && !noCmpr, buf = obj[p], crc = UZIP.crc.crc(buf,0,buf.length);
|
|
120
|
+
zpd[p] = { cpr:cpr, usize:buf.length, crc:crc, file: (cpr ? UZIP.deflateRaw(buf) : buf) }; }
|
|
121
|
+
|
|
122
|
+
for(let p in zpd) tot += zpd[p].file.length + 30 + 46 + 2*UZIP.bin.sizeUTF8(p);
|
|
123
|
+
tot += 22;
|
|
124
|
+
|
|
125
|
+
let data = new Uint8Array(tot), o = 0;
|
|
126
|
+
let fof = []
|
|
127
|
+
|
|
128
|
+
for(let p in zpd) {
|
|
129
|
+
let file = zpd[p]; fof.push(o);
|
|
130
|
+
o = UZIP._writeHeader(data, o, p, file, 0);
|
|
131
|
+
}
|
|
132
|
+
let i=0, ioff = o;
|
|
133
|
+
for(let p in zpd) {
|
|
134
|
+
let file = zpd[p]; fof.push(o);
|
|
135
|
+
o = UZIP._writeHeader(data, o, p, file, 1, fof[i++]);
|
|
136
|
+
}
|
|
137
|
+
let csize = o-ioff;
|
|
138
|
+
|
|
139
|
+
wUi(data, o, 0x06054b50); o+=4;
|
|
140
|
+
o += 4; // disks
|
|
141
|
+
wUs(data, o, i); o += 2;
|
|
142
|
+
wUs(data, o, i); o += 2; // number of c d records
|
|
143
|
+
wUi(data, o, csize); o += 4;
|
|
144
|
+
wUi(data, o, ioff ); o += 4;
|
|
145
|
+
o += 2;
|
|
146
|
+
return data.buffer;
|
|
147
|
+
}
|
|
148
|
+
// no need to compress .PNG, .ZIP, .JPEG ....
|
|
149
|
+
UZIP._noNeed = function(fn) { let ext = fn.split(".").pop().toLowerCase(); return "png,jpg,jpeg,zip".indexOf(ext)!=-1; }
|
|
150
|
+
|
|
151
|
+
UZIP._writeHeader = function(data, o, p, obj, t, roff)
|
|
152
|
+
{
|
|
153
|
+
let wUi = UZIP.bin.writeUint, wUs = UZIP.bin.writeUshort;
|
|
154
|
+
let file = obj.file;
|
|
155
|
+
|
|
156
|
+
wUi(data, o, t==0 ? 0x04034b50 : 0x02014b50); o+=4; // sign
|
|
157
|
+
if(t==1) o+=2; // ver made by
|
|
158
|
+
wUs(data, o, 20); o+=2; // ver
|
|
159
|
+
wUs(data, o, 0); o+=2; // gflip
|
|
160
|
+
wUs(data, o, obj.cpr?8:0); o+=2; // cmpr
|
|
161
|
+
|
|
162
|
+
wUi(data, o, 0); o+=4; // time
|
|
163
|
+
wUi(data, o, obj.crc); o+=4; // crc32
|
|
164
|
+
wUi(data, o, file.length); o+=4; // csize
|
|
165
|
+
wUi(data, o, obj.usize); o+=4; // usize
|
|
166
|
+
|
|
167
|
+
wUs(data, o, UZIP.bin.sizeUTF8(p)); o+=2; // nlen
|
|
168
|
+
wUs(data, o, 0); o+=2; // elen
|
|
169
|
+
|
|
170
|
+
if(t==1) {
|
|
171
|
+
o += 2; // comment length
|
|
172
|
+
o += 2; // disk number
|
|
173
|
+
o += 6; // attributes
|
|
174
|
+
wUi(data, o, roff); o+=4; // usize
|
|
175
|
+
}
|
|
176
|
+
let nlen = UZIP.bin.writeUTF8(data, o, p); o+= nlen;
|
|
177
|
+
if(t==0) { data.set(file, o); o += file.length; }
|
|
178
|
+
return o;
|
|
179
|
+
}
|
|
180
|
+
UZIP.crc = {
|
|
181
|
+
table : ( function() {
|
|
182
|
+
let tab = new Uint32Array(256);
|
|
183
|
+
for (let n=0; n<256; n++) {
|
|
184
|
+
let c = n;
|
|
185
|
+
for (let k=0; k<8; k++) {
|
|
186
|
+
if (c & 1) c = 0xedb88320 ^ (c >>> 1);
|
|
187
|
+
else c = c >>> 1;
|
|
188
|
+
}
|
|
189
|
+
tab[n] = c; }
|
|
190
|
+
return tab; })(),
|
|
191
|
+
update : function(c, buf, off, len) {
|
|
192
|
+
for (let i=0; i<len; i++) c = UZIP.crc.table[(c ^ buf[off+i]) & 0xff] ^ (c >>> 8);
|
|
193
|
+
return c;
|
|
194
|
+
},
|
|
195
|
+
crc : function(b,o,l) { return UZIP.crc.update(0xffffffff,b,o,l) ^ 0xffffffff; }
|
|
196
|
+
}
|
|
197
|
+
UZIP.adler = function(data,o,len) {
|
|
198
|
+
let a = 1, b = 0;
|
|
199
|
+
let off = o, end=o+len;
|
|
200
|
+
while(off<end) {
|
|
201
|
+
let eend = Math.min(off+5552, end);
|
|
202
|
+
while(off<eend) {
|
|
203
|
+
a += data[off++];
|
|
204
|
+
b += a;
|
|
205
|
+
}
|
|
206
|
+
a=a%65521;
|
|
207
|
+
b=b%65521;
|
|
208
|
+
}
|
|
209
|
+
return (b << 16) | a;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
UZIP.bin = {
|
|
213
|
+
readUshort : function(buff,p) { return (buff[p]) | (buff[p+1]<<8); },
|
|
214
|
+
writeUshort: function(buff,p,n){ buff[p] = (n)&255; buff[p+1] = (n>>8)&255; },
|
|
215
|
+
readUint : function(buff,p) { return (buff[p+3]*(256*256*256)) + ((buff[p+2]<<16) | (buff[p+1]<< 8) | buff[p]); },
|
|
216
|
+
writeUint : function(buff,p,n){ buff[p]=n&255; buff[p+1]=(n>>8)&255; buff[p+2]=(n>>16)&255; buff[p+3]=(n>>24)&255; },
|
|
217
|
+
readASCII : function(buff,p,l){ let s = ""; for(let i=0; i<l; i++) s += String.fromCharCode(buff[p+i]); return s; },
|
|
218
|
+
writeASCII : function(data,p,s){ for(let i=0; i<s.length; i++) data[p+i] = s.charCodeAt(i); },
|
|
219
|
+
pad : function(n) { return n.length < 2 ? "0" + n : n; },
|
|
220
|
+
readUTF8 : function(buff, p, l) {
|
|
221
|
+
let s = "", ns;
|
|
222
|
+
for(let i=0; i<l; i++) s += "%" + UZIP.bin.pad(buff[p+i].toString(16));
|
|
223
|
+
try { ns = decodeURIComponent(s); }
|
|
224
|
+
catch(e) { return UZIP.bin.readASCII(buff, p, l); }
|
|
225
|
+
return ns;
|
|
226
|
+
},
|
|
227
|
+
writeUTF8 : function(buff, p, str) {
|
|
228
|
+
let strl = str.length, i=0;
|
|
229
|
+
for(let ci=0; ci<strl; ci++)
|
|
230
|
+
{
|
|
231
|
+
let code = str.charCodeAt(ci);
|
|
232
|
+
if ((code&(0xffffffff-(1<< 7)+1))==0) { buff[p+i] = ( code ); i++; }
|
|
233
|
+
else if((code&(0xffffffff-(1<<11)+1))==0) { buff[p+i] = (192|(code>> 6)); buff[p+i+1] = (128|((code>> 0)&63)); i+=2; }
|
|
234
|
+
else if((code&(0xffffffff-(1<<16)+1))==0) { buff[p+i] = (224|(code>>12)); buff[p+i+1] = (128|((code>> 6)&63)); buff[p+i+2] = (128|((code>>0)&63)); i+=3; }
|
|
235
|
+
else if((code&(0xffffffff-(1<<21)+1))==0) { buff[p+i] = (240|(code>>18)); buff[p+i+1] = (128|((code>>12)&63)); buff[p+i+2] = (128|((code>>6)&63)); buff[p+i+3] = (128|((code>>0)&63)); i+=4; }
|
|
236
|
+
else throw "e";
|
|
237
|
+
}
|
|
238
|
+
return i;
|
|
239
|
+
},
|
|
240
|
+
sizeUTF8 : function(str) {
|
|
241
|
+
let strl = str.length, i=0;
|
|
242
|
+
for(let ci=0; ci<strl; ci++)
|
|
243
|
+
{
|
|
244
|
+
let code = str.charCodeAt(ci);
|
|
245
|
+
if ((code&(0xffffffff-(1<< 7)+1))==0) { i++ ; }
|
|
246
|
+
else if((code&(0xffffffff-(1<<11)+1))==0) { i+=2; }
|
|
247
|
+
else if((code&(0xffffffff-(1<<16)+1))==0) { i+=3; }
|
|
248
|
+
else if((code&(0xffffffff-(1<<21)+1))==0) { i+=4; }
|
|
249
|
+
else throw "e";
|
|
250
|
+
}
|
|
251
|
+
return i;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
UZIP.F = {};
|
|
255
|
+
|
|
256
|
+
UZIP.F.deflateRaw = function(data, out, opos, lvl) {
|
|
257
|
+
let opts = [
|
|
258
|
+
/*
|
|
259
|
+
ush good_length; /* reduce lazy search above this match length
|
|
260
|
+
ush max_lazy; /* do not perform lazy search above this match length
|
|
261
|
+
ush nice_length; /* quit search above this match length
|
|
262
|
+
*/
|
|
263
|
+
/* good lazy nice chain */
|
|
264
|
+
/* 0 */ [ 0, 0, 0, 0,0], /* store only */
|
|
265
|
+
/* 1 */ [ 4, 4, 8, 4,0], /* max speed, no lazy matches */
|
|
266
|
+
/* 2 */ [ 4, 5, 16, 8,0],
|
|
267
|
+
/* 3 */ [ 4, 6, 16, 16,0],
|
|
268
|
+
|
|
269
|
+
/* 4 */ [ 4, 10, 16, 32,0], /* lazy matches */
|
|
270
|
+
/* 5 */ [ 8, 16, 32, 32,0],
|
|
271
|
+
/* 6 */ [ 8, 16, 128, 128,0],
|
|
272
|
+
/* 7 */ [ 8, 32, 128, 256,0],
|
|
273
|
+
/* 8 */ [32, 128, 258, 1024,1],
|
|
274
|
+
/* 9 */ [32, 258, 258, 4096,1]]; /* max compression */
|
|
275
|
+
|
|
276
|
+
let opt = opts[lvl];
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
let U = UZIP.F.U, goodIndex = UZIP.F._goodIndex, hash = UZIP.F._hash, putsE = UZIP.F._putsE;
|
|
280
|
+
let i = 0, pos = opos<<3, cvrd = 0, dlen = data.length;
|
|
281
|
+
|
|
282
|
+
if(lvl==0) {
|
|
283
|
+
while(i<dlen) { let len = Math.min(0xffff, dlen-i);
|
|
284
|
+
putsE(out, pos, (i+len==dlen ? 1 : 0)); pos = UZIP.F._copyExact(data, i, len, out, pos+8); i += len; }
|
|
285
|
+
return pos>>>3;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
let lits = U.lits, strt=U.strt, prev=U.prev, li=0, lc=0, bs=0, ebits=0, c=0, nc=0; // last_item, literal_count, block_start
|
|
289
|
+
if(dlen>2) { nc=UZIP.F._hash(data,0); strt[nc]=0; }
|
|
290
|
+
let nmch=0,nmci=0;
|
|
291
|
+
|
|
292
|
+
for(i=0; i<dlen; i++) {
|
|
293
|
+
c = nc;
|
|
294
|
+
//*
|
|
295
|
+
if(i+1<dlen-2) {
|
|
296
|
+
nc = UZIP.F._hash(data, i+1);
|
|
297
|
+
let ii = ((i+1)&0x7fff);
|
|
298
|
+
prev[ii]=strt[nc];
|
|
299
|
+
strt[nc]=ii;
|
|
300
|
+
} //*/
|
|
301
|
+
if(cvrd<=i) {
|
|
302
|
+
if((li>14000 || lc>26697) && (dlen-i)>100) {
|
|
303
|
+
if(cvrd<i) { lits[li]=i-cvrd; li+=2; cvrd=i; }
|
|
304
|
+
pos = UZIP.F._writeBlock(((i==dlen-1) || (cvrd==dlen))?1:0, lits, li, ebits, data,bs,i-bs, out, pos); li=lc=ebits=0; bs=i;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
let mch = 0;
|
|
308
|
+
//if(nmci==i) mch= nmch; else
|
|
309
|
+
if(i<dlen-2) mch = UZIP.F._bestMatch(data, i, prev, c, Math.min(opt[2],dlen-i), opt[3]);
|
|
310
|
+
/*
|
|
311
|
+
if(mch!=0 && opt[4]==1 && (mch>>>16)<opt[1] && i+1<dlen-2) {
|
|
312
|
+
nmch = UZIP.F._bestMatch(data, i+1, prev, nc, opt[2], opt[3]); nmci=i+1;
|
|
313
|
+
//let mch2 = UZIP.F._bestMatch(data, i+2, prev, nnc); //nmci=i+1;
|
|
314
|
+
if((nmch>>>16)>(mch>>>16)) mch=0;
|
|
315
|
+
}//*/
|
|
316
|
+
let len = mch>>>16, dst = mch&0xffff; //if(i-dst<0) throw "e";
|
|
317
|
+
if(mch!=0) {
|
|
318
|
+
let len = mch>>>16, dst = mch&0xffff; //if(i-dst<0) throw "e";
|
|
319
|
+
let lgi = goodIndex(len, U.of0); U.lhst[257+lgi]++;
|
|
320
|
+
let dgi = goodIndex(dst, U.df0); U.dhst[ dgi]++; ebits += U.exb[lgi] + U.dxb[dgi];
|
|
321
|
+
lits[li] = (len<<23)|(i-cvrd); lits[li+1] = (dst<<16)|(lgi<<8)|dgi; li+=2;
|
|
322
|
+
cvrd = i + len;
|
|
323
|
+
}
|
|
324
|
+
else { U.lhst[data[i]]++; }
|
|
325
|
+
lc++;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if(bs!=i || data.length==0) {
|
|
329
|
+
if(cvrd<i) { lits[li]=i-cvrd; li+=2; cvrd=i; }
|
|
330
|
+
pos = UZIP.F._writeBlock(1, lits, li, ebits, data,bs,i-bs, out, pos); li=0; lc=0; li=lc=ebits=0; bs=i;
|
|
331
|
+
}
|
|
332
|
+
while((pos&7)!=0) pos++;
|
|
333
|
+
return pos>>>3;
|
|
334
|
+
}
|
|
335
|
+
UZIP.F._bestMatch = function(data, i, prev, c, nice, chain) {
|
|
336
|
+
let ci = (i&0x7fff), pi=prev[ci];
|
|
337
|
+
//console.log("----", i);
|
|
338
|
+
let dif = ((ci-pi + (1<<15)) & 0x7fff); if(pi==ci || c!=UZIP.F._hash(data,i-dif)) return 0;
|
|
339
|
+
let tl=0, td=0; // top length, top distance
|
|
340
|
+
let dlim = Math.min(0x7fff, i);
|
|
341
|
+
while(dif<=dlim && --chain!=0 && pi!=ci /*&& c==UZIP.F._hash(data,i-dif)*/) {
|
|
342
|
+
if(tl==0 || (data[i+tl]==data[i+tl-dif])) {
|
|
343
|
+
let cl = UZIP.F._howLong(data, i, dif);
|
|
344
|
+
if(cl>tl) {
|
|
345
|
+
tl=cl; td=dif; if(tl>=nice) break; //*
|
|
346
|
+
if(dif+2<cl) cl = dif+2;
|
|
347
|
+
let maxd = 0; // pi does not point to the start of the word
|
|
348
|
+
for(let j=0; j<cl-2; j++) {
|
|
349
|
+
let ei = (i-dif+j+ (1<<15)) & 0x7fff;
|
|
350
|
+
let li = prev[ei];
|
|
351
|
+
let curd = (ei-li + (1<<15)) & 0x7fff;
|
|
352
|
+
if(curd>maxd) { maxd=curd; pi = ei; }
|
|
353
|
+
} //*/
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
ci=pi; pi = prev[ci];
|
|
358
|
+
dif += ((ci-pi + (1<<15)) & 0x7fff);
|
|
359
|
+
}
|
|
360
|
+
return (tl<<16)|td;
|
|
361
|
+
}
|
|
362
|
+
UZIP.F._howLong = function(data, i, dif) {
|
|
363
|
+
if(data[i]!=data[i-dif] || data[i+1]!=data[i+1-dif] || data[i+2]!=data[i+2-dif]) return 0;
|
|
364
|
+
let oi=i, l = Math.min(data.length, i+258); i+=3;
|
|
365
|
+
//while(i+4<l && data[i]==data[i-dif] && data[i+1]==data[i+1-dif] && data[i+2]==data[i+2-dif] && data[i+3]==data[i+3-dif]) i+=4;
|
|
366
|
+
while(i<l && data[i]==data[i-dif]) i++;
|
|
367
|
+
return i-oi;
|
|
368
|
+
}
|
|
369
|
+
UZIP.F._hash = function(data, i) {
|
|
370
|
+
return (((data[i]<<8) | data[i+1])+(data[i+2]<<4))&0xffff;
|
|
371
|
+
//let hash_shift = 0, hash_mask = 255;
|
|
372
|
+
//let h = data[i+1] % 251;
|
|
373
|
+
//h = (((h << 8) + data[i+2]) % 251);
|
|
374
|
+
//h = (((h << 8) + data[i+2]) % 251);
|
|
375
|
+
//h = ((h<<hash_shift) ^ (c) ) & hash_mask;
|
|
376
|
+
//return h | (data[i]<<8);
|
|
377
|
+
//return (data[i] | (data[i+1]<<8));
|
|
378
|
+
}
|
|
379
|
+
//UZIP.___toth = 0;
|
|
380
|
+
UZIP.saved = 0;
|
|
381
|
+
UZIP.F._writeBlock = function(BFINAL, lits, li, ebits, data,o0,l0, out, pos) {
|
|
382
|
+
let U = UZIP.F.U, putsF = UZIP.F._putsF, putsE = UZIP.F._putsE;
|
|
383
|
+
|
|
384
|
+
//*
|
|
385
|
+
let T, ML, MD, MH, numl, numd, numh, lset, dset; U.lhst[256]++;
|
|
386
|
+
T = UZIP.F.getTrees(); ML=T[0]; MD=T[1]; MH=T[2]; numl=T[3]; numd=T[4]; numh=T[5]; lset=T[6]; dset=T[7];
|
|
387
|
+
|
|
388
|
+
let cstSize = (((pos+3)&7)==0 ? 0 : 8-((pos+3)&7)) + 32 + (l0<<3);
|
|
389
|
+
let fxdSize = ebits + UZIP.F.contSize(U.fltree, U.lhst) + UZIP.F.contSize(U.fdtree, U.dhst);
|
|
390
|
+
let dynSize = ebits + UZIP.F.contSize(U.ltree , U.lhst) + UZIP.F.contSize(U.dtree , U.dhst);
|
|
391
|
+
dynSize += 14 + 3*numh + UZIP.F.contSize(U.itree, U.ihst) + (U.ihst[16]*2 + U.ihst[17]*3 + U.ihst[18]*7);
|
|
392
|
+
|
|
393
|
+
for(let j=0; j<286; j++) U.lhst[j]=0; for(let j=0; j<30; j++) U.dhst[j]=0; for(let j=0; j<19; j++) U.ihst[j]=0;
|
|
394
|
+
//*/
|
|
395
|
+
let BTYPE = (cstSize<fxdSize && cstSize<dynSize) ? 0 : ( fxdSize<dynSize ? 1 : 2 );
|
|
396
|
+
putsF(out, pos, BFINAL); putsF(out, pos+1, BTYPE); pos+=3;
|
|
397
|
+
|
|
398
|
+
let opos = pos;
|
|
399
|
+
if(BTYPE==0) {
|
|
400
|
+
while((pos&7)!=0) pos++;
|
|
401
|
+
pos = UZIP.F._copyExact(data, o0, l0, out, pos);
|
|
402
|
+
}
|
|
403
|
+
else {
|
|
404
|
+
let ltree, dtree;
|
|
405
|
+
if(BTYPE==1) { ltree=U.fltree; dtree=U.fdtree; }
|
|
406
|
+
if(BTYPE==2) {
|
|
407
|
+
UZIP.F.makeCodes(U.ltree, ML); UZIP.F.revCodes(U.ltree, ML);
|
|
408
|
+
UZIP.F.makeCodes(U.dtree, MD); UZIP.F.revCodes(U.dtree, MD);
|
|
409
|
+
UZIP.F.makeCodes(U.itree, MH); UZIP.F.revCodes(U.itree, MH);
|
|
410
|
+
|
|
411
|
+
ltree = U.ltree; dtree = U.dtree;
|
|
412
|
+
|
|
413
|
+
putsE(out, pos,numl-257); pos+=5; // 286
|
|
414
|
+
putsE(out, pos,numd- 1); pos+=5; // 30
|
|
415
|
+
putsE(out, pos,numh- 4); pos+=4; // 19
|
|
416
|
+
|
|
417
|
+
for(let i=0; i<numh; i++) putsE(out, pos+i*3, U.itree[(U.ordr[i]<<1)+1]); pos+=3* numh;
|
|
418
|
+
pos = UZIP.F._codeTiny(lset, U.itree, out, pos);
|
|
419
|
+
pos = UZIP.F._codeTiny(dset, U.itree, out, pos);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
let off=o0;
|
|
423
|
+
for(let si=0; si<li; si+=2) {
|
|
424
|
+
let qb=lits[si], len=(qb>>>23), end = off+(qb&((1<<23)-1));
|
|
425
|
+
while(off<end) pos = UZIP.F._writeLit(data[off++], ltree, out, pos);
|
|
426
|
+
|
|
427
|
+
if(len!=0) {
|
|
428
|
+
let qc = lits[si+1], dst=(qc>>16), lgi=(qc>>8)&255, dgi=(qc&255);
|
|
429
|
+
pos = UZIP.F._writeLit(257+lgi, ltree, out, pos);
|
|
430
|
+
putsE(out, pos, len-U.of0[lgi]); pos+=U.exb[lgi];
|
|
431
|
+
|
|
432
|
+
pos = UZIP.F._writeLit(dgi, dtree, out, pos);
|
|
433
|
+
putsF(out, pos, dst-U.df0[dgi]); pos+=U.dxb[dgi]; off+=len;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
pos = UZIP.F._writeLit(256, ltree, out, pos);
|
|
437
|
+
}
|
|
438
|
+
//console.log(pos-opos, fxdSize, dynSize, cstSize);
|
|
439
|
+
return pos;
|
|
440
|
+
}
|
|
441
|
+
UZIP.F._copyExact = function(data,off,len,out,pos) {
|
|
442
|
+
let p8 = (pos>>>3);
|
|
443
|
+
out[p8]=(len); out[p8+1]=(len>>>8); out[p8+2]=255-out[p8]; out[p8+3]=255-out[p8+1]; p8+=4;
|
|
444
|
+
out.set(new Uint8Array(data.buffer, off, len), p8);
|
|
445
|
+
//for(let i=0; i<len; i++) out[p8+i]=data[off+i];
|
|
446
|
+
return pos + ((len+4)<<3);
|
|
447
|
+
}
|
|
448
|
+
/*
|
|
449
|
+
Interesting facts:
|
|
450
|
+
- decompressed block can have bytes, which do not occur in a Huffman tree (copied from the previous block by reference)
|
|
451
|
+
*/
|
|
452
|
+
|
|
453
|
+
UZIP.F.getTrees = function() {
|
|
454
|
+
let U = UZIP.F.U;
|
|
455
|
+
let ML = UZIP.F._hufTree(U.lhst, U.ltree, 15);
|
|
456
|
+
let MD = UZIP.F._hufTree(U.dhst, U.dtree, 15);
|
|
457
|
+
let lset = [], numl = UZIP.F._lenCodes(U.ltree, lset);
|
|
458
|
+
let dset = [], numd = UZIP.F._lenCodes(U.dtree, dset);
|
|
459
|
+
for(let i=0; i<lset.length; i+=2) U.ihst[lset[i]]++;
|
|
460
|
+
for(let i=0; i<dset.length; i+=2) U.ihst[dset[i]]++;
|
|
461
|
+
let MH = UZIP.F._hufTree(U.ihst, U.itree, 7);
|
|
462
|
+
let numh = 19; while(numh>4 && U.itree[(U.ordr[numh-1]<<1)+1]==0) numh--;
|
|
463
|
+
return [ML, MD, MH, numl, numd, numh, lset, dset];
|
|
464
|
+
}
|
|
465
|
+
UZIP.F.getSecond= function(a) { let b=[]; for(let i=0; i<a.length; i+=2) b.push (a[i+1]); return b; }
|
|
466
|
+
UZIP.F.nonZero = function(a) { let b= ""; for(let i=0; i<a.length; i+=2) if(a[i+1]!=0)b+=(i>>1)+","; return b; }
|
|
467
|
+
UZIP.F.contSize = function(tree, hst) { let s=0; for(let i=0; i<hst.length; i++) s+= hst[i]*tree[(i<<1)+1]; return s; }
|
|
468
|
+
UZIP.F._codeTiny = function(set, tree, out, pos) {
|
|
469
|
+
for(let i=0; i<set.length; i+=2) {
|
|
470
|
+
let l = set[i], rst = set[i+1]; //console.log(l, pos, tree[(l<<1)+1]);
|
|
471
|
+
pos = UZIP.F._writeLit(l, tree, out, pos);
|
|
472
|
+
let rsl = l==16 ? 2 : (l==17 ? 3 : 7);
|
|
473
|
+
if(l>15) { UZIP.F._putsE(out, pos, rst, rsl); pos+=rsl; }
|
|
474
|
+
}
|
|
475
|
+
return pos;
|
|
476
|
+
}
|
|
477
|
+
UZIP.F._lenCodes = function(tree, set) {
|
|
478
|
+
let len=tree.length; while(len!=2 && tree[len-1]==0) len-=2; // when no distances, keep one code with length 0
|
|
479
|
+
for(let i=0; i<len; i+=2) {
|
|
480
|
+
let l = tree[i+1], nxt = (i+3<len ? tree[i+3]:-1), nnxt = (i+5<len ? tree[i+5]:-1), prv = (i==0 ? -1 : tree[i-1]);
|
|
481
|
+
if(l==0 && nxt==l && nnxt==l) {
|
|
482
|
+
let lz = i+5;
|
|
483
|
+
while(lz+2<len && tree[lz+2]==l) lz+=2;
|
|
484
|
+
let zc = Math.min((lz+1-i)>>>1, 138);
|
|
485
|
+
if(zc<11) set.push(17, zc-3);
|
|
486
|
+
else set.push(18, zc-11);
|
|
487
|
+
i += zc*2-2;
|
|
488
|
+
}
|
|
489
|
+
else if(l==prv && nxt==l && nnxt==l) {
|
|
490
|
+
let lz = i+5;
|
|
491
|
+
while(lz+2<len && tree[lz+2]==l) lz+=2;
|
|
492
|
+
let zc = Math.min((lz+1-i)>>>1, 6);
|
|
493
|
+
set.push(16, zc-3);
|
|
494
|
+
i += zc*2-2;
|
|
495
|
+
}
|
|
496
|
+
else set.push(l, 0);
|
|
497
|
+
}
|
|
498
|
+
return len>>>1;
|
|
499
|
+
}
|
|
500
|
+
UZIP.F._hufTree = function(hst, tree, MAXL) {
|
|
501
|
+
let list=[], hl = hst.length, tl=tree.length, i=0;
|
|
502
|
+
for(i=0; i<tl; i+=2) { tree[i]=0; tree[i+1]=0; }
|
|
503
|
+
for(i=0; i<hl; i++) if(hst[i]!=0) list.push({lit:i, f:hst[i]});
|
|
504
|
+
let end = list.length, l2=list.slice(0);
|
|
505
|
+
if(end==0) return 0; // empty histogram (usually for dist)
|
|
506
|
+
if(end==1) { let lit=list[0].lit, l2=lit==0?1:0; tree[(lit<<1)+1]=1; tree[(l2<<1)+1]=1; return 1; }
|
|
507
|
+
list.sort(function(a,b){return a.f-b.f;});
|
|
508
|
+
let a=list[0], b=list[1], i0=0, i1=1, i2=2; list[0]={lit:-1,f:a.f+b.f,l:a,r:b,d:0};
|
|
509
|
+
while(i1!=end-1) {
|
|
510
|
+
if(i0!=i1 && (i2==end || list[i0].f<list[i2].f)) { a=list[i0++]; } else { a=list[i2++]; }
|
|
511
|
+
if(i0!=i1 && (i2==end || list[i0].f<list[i2].f)) { b=list[i0++]; } else { b=list[i2++]; }
|
|
512
|
+
list[i1++]={lit:-1,f:a.f+b.f, l:a,r:b};
|
|
513
|
+
}
|
|
514
|
+
let maxl = UZIP.F.setDepth(list[i1-1], 0);
|
|
515
|
+
if(maxl>MAXL) { UZIP.F.restrictDepth(l2, MAXL, maxl); maxl = MAXL; }
|
|
516
|
+
for(i=0; i<end; i++) tree[(l2[i].lit<<1)+1]=l2[i].d;
|
|
517
|
+
return maxl;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
UZIP.F.setDepth = function(t, d) {
|
|
521
|
+
if(t.lit!=-1) { t.d=d; return d; }
|
|
522
|
+
return Math.max( UZIP.F.setDepth(t.l, d+1), UZIP.F.setDepth(t.r, d+1) );
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
UZIP.F.restrictDepth = function(dps, MD, maxl) {
|
|
526
|
+
let i=0, bCost=1<<(maxl-MD), dbt=0;
|
|
527
|
+
dps.sort(function(a,b){return b.d==a.d ? a.f-b.f : b.d-a.d;});
|
|
528
|
+
|
|
529
|
+
for(i=0; i<dps.length; i++) if(dps[i].d>MD) { let od=dps[i].d; dps[i].d=MD; dbt+=bCost-(1<<(maxl-od)); } else break;
|
|
530
|
+
dbt = dbt>>>(maxl-MD);
|
|
531
|
+
while(dbt>0) { let od=dps[i].d; if(od<MD) { dps[i].d++; dbt-=(1<<(MD-od-1)); } else i++; }
|
|
532
|
+
for(; i>=0; i--) if(dps[i].d==MD && dbt<0) { dps[i].d--; dbt++; } if(dbt!=0) console.log("debt left");
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
UZIP.F._goodIndex = function(v, arr) {
|
|
536
|
+
let i=0; if(arr[i|16]<=v) i|=16; if(arr[i|8]<=v) i|=8; if(arr[i|4]<=v) i|=4; if(arr[i|2]<=v) i|=2; if(arr[i|1]<=v) i|=1; return i;
|
|
537
|
+
}
|
|
538
|
+
UZIP.F._writeLit = function(ch, ltree, out, pos) {
|
|
539
|
+
UZIP.F._putsF(out, pos, ltree[ch<<1]);
|
|
540
|
+
return pos+ltree[(ch<<1)+1];
|
|
541
|
+
}
|
|
542
|
+
UZIP.F.inflate = function(data, buf) {
|
|
543
|
+
let u8=Uint8Array;
|
|
544
|
+
if(data[0]==3 && data[1]==0) return (buf ? buf : new u8(0));
|
|
545
|
+
let F=UZIP.F, bitsF = F._bitsF, bitsE = F._bitsE, decodeTiny = F._decodeTiny, makeCodes = F.makeCodes, codes2map=F.codes2map, get17 = F._get17;
|
|
546
|
+
let U = F.U;
|
|
547
|
+
|
|
548
|
+
let noBuf = (buf==null);
|
|
549
|
+
if(noBuf) buf = new u8((data.length>>>2)<<3);
|
|
550
|
+
|
|
551
|
+
let BFINAL=0, BTYPE=0, HLIT=0, HDIST=0, HCLEN=0, ML=0, MD=0;
|
|
552
|
+
let off = 0, pos = 0;
|
|
553
|
+
let lmap, dmap;
|
|
554
|
+
|
|
555
|
+
while(BFINAL==0) {
|
|
556
|
+
BFINAL = bitsF(data, pos , 1);
|
|
557
|
+
BTYPE = bitsF(data, pos+1, 2); pos+=3;
|
|
558
|
+
//console.log(BFINAL, BTYPE);
|
|
559
|
+
|
|
560
|
+
if(BTYPE==0) {
|
|
561
|
+
if((pos&7)!=0) pos+=8-(pos&7);
|
|
562
|
+
let p8 = (pos>>>3)+4, len = data[p8-4]|(data[p8-3]<<8); //console.log(len);//bitsF(data, pos, 16),
|
|
563
|
+
if(noBuf) buf=UZIP.F._check(buf, off+len);
|
|
564
|
+
buf.set(new u8(data.buffer, data.byteOffset+p8, len), off);
|
|
565
|
+
//for(let i=0; i<len; i++) buf[off+i] = data[p8+i];
|
|
566
|
+
//for(let i=0; i<len; i++) if(buf[off+i] != data[p8+i]) throw "e";
|
|
567
|
+
pos = ((p8+len)<<3); off+=len; continue;
|
|
568
|
+
}
|
|
569
|
+
if(noBuf) buf=UZIP.F._check(buf, off+(1<<17)); // really not enough in many cases (but PNG and ZIP provide buffer in advance)
|
|
570
|
+
if(BTYPE==1) { lmap = U.flmap; dmap = U.fdmap; ML = (1<<9)-1; MD = (1<<5)-1; }
|
|
571
|
+
if(BTYPE==2) {
|
|
572
|
+
HLIT = bitsE(data, pos , 5)+257;
|
|
573
|
+
HDIST = bitsE(data, pos+ 5, 5)+ 1;
|
|
574
|
+
HCLEN = bitsE(data, pos+10, 4)+ 4; pos+=14;
|
|
575
|
+
|
|
576
|
+
let ppos = pos;
|
|
577
|
+
for(let i=0; i<38; i+=2) { U.itree[i]=0; U.itree[i+1]=0; }
|
|
578
|
+
let tl = 1;
|
|
579
|
+
for(let i=0; i<HCLEN; i++) { let l=bitsE(data, pos+i*3, 3); U.itree[(U.ordr[i]<<1)+1] = l; if(l>tl)tl=l; } pos+=3*HCLEN; //console.log(itree);
|
|
580
|
+
makeCodes(U.itree, tl);
|
|
581
|
+
codes2map(U.itree, tl, U.imap);
|
|
582
|
+
|
|
583
|
+
lmap = U.lmap; dmap = U.dmap;
|
|
584
|
+
|
|
585
|
+
pos = decodeTiny(U.imap, (1<<tl)-1, HLIT+HDIST, data, pos, U.ttree);
|
|
586
|
+
let mx0 = F._copyOut(U.ttree, 0, HLIT , U.ltree); ML = (1<<mx0)-1;
|
|
587
|
+
let mx1 = F._copyOut(U.ttree, HLIT, HDIST, U.dtree); MD = (1<<mx1)-1;
|
|
588
|
+
|
|
589
|
+
//let ml = decodeTiny(U.imap, (1<<tl)-1, HLIT , data, pos, U.ltree); ML = (1<<(ml>>>24))-1; pos+=(ml&0xffffff);
|
|
590
|
+
makeCodes(U.ltree, mx0);
|
|
591
|
+
codes2map(U.ltree, mx0, lmap);
|
|
592
|
+
|
|
593
|
+
//let md = decodeTiny(U.imap, (1<<tl)-1, HDIST, data, pos, U.dtree); MD = (1<<(md>>>24))-1; pos+=(md&0xffffff);
|
|
594
|
+
makeCodes(U.dtree, mx1);
|
|
595
|
+
codes2map(U.dtree, mx1, dmap);
|
|
596
|
+
}
|
|
597
|
+
//let ooff=off, opos=pos;
|
|
598
|
+
while(true) {
|
|
599
|
+
let code = lmap[get17(data, pos) & ML]; pos += code&15;
|
|
600
|
+
let lit = code>>>4; //U.lhst[lit]++;
|
|
601
|
+
if((lit>>>8)==0) { buf[off++] = lit; }
|
|
602
|
+
else if(lit==256) { break; }
|
|
603
|
+
else {
|
|
604
|
+
let end = off+lit-254;
|
|
605
|
+
if(lit>264) { let ebs = U.ldef[lit-257]; end = off + (ebs>>>3) + bitsE(data, pos, ebs&7); pos += ebs&7; }
|
|
606
|
+
//UZIP.F.dst[end-off]++;
|
|
607
|
+
|
|
608
|
+
let dcode = dmap[get17(data, pos) & MD]; pos += dcode&15;
|
|
609
|
+
let dlit = dcode>>>4;
|
|
610
|
+
let dbs = U.ddef[dlit], dst = (dbs>>>4) + bitsF(data, pos, dbs&15); pos += dbs&15;
|
|
611
|
+
|
|
612
|
+
//let o0 = off-dst, stp = Math.min(end-off, dst);
|
|
613
|
+
//if(stp>20) while(off<end) { buf.copyWithin(off, o0, o0+stp); off+=stp; } else
|
|
614
|
+
//if(end-dst<=off) buf.copyWithin(off, off-dst, end-dst); else
|
|
615
|
+
//if(dst==1) buf.fill(buf[off-1], off, end); else
|
|
616
|
+
if(noBuf) buf=UZIP.F._check(buf, off+(1<<17));
|
|
617
|
+
while(off<end) { buf[off]=buf[off++-dst]; buf[off]=buf[off++-dst]; buf[off]=buf[off++-dst]; buf[off]=buf[off++-dst]; }
|
|
618
|
+
off=end;
|
|
619
|
+
//while(off!=end) { buf[off]=buf[off++-dst]; }
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
//console.log(off-ooff, (pos-opos)>>>3);
|
|
623
|
+
}
|
|
624
|
+
//console.log(UZIP.F.dst);
|
|
625
|
+
//console.log(tlen, dlen, off-tlen+tcnt);
|
|
626
|
+
return buf.length==off ? buf : buf.slice(0,off);
|
|
627
|
+
}
|
|
628
|
+
UZIP.F._check=function(buf, len) {
|
|
629
|
+
let bl=buf.length; if(len<=bl) return buf;
|
|
630
|
+
let nbuf = new Uint8Array(Math.max(bl<<1,len)); nbuf.set(buf,0);
|
|
631
|
+
//for(let i=0; i<bl; i+=4) { nbuf[i]=buf[i]; nbuf[i+1]=buf[i+1]; nbuf[i+2]=buf[i+2]; nbuf[i+3]=buf[i+3]; }
|
|
632
|
+
return nbuf;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
UZIP.F._decodeTiny = function(lmap, LL, len, data, pos, tree) {
|
|
636
|
+
let bitsE = UZIP.F._bitsE, get17 = UZIP.F._get17;
|
|
637
|
+
let i = 0;
|
|
638
|
+
while(i<len) {
|
|
639
|
+
let code = lmap[get17(data, pos)&LL]; pos+=code&15;
|
|
640
|
+
let lit = code>>>4;
|
|
641
|
+
if(lit<=15) { tree[i]=lit; i++; }
|
|
642
|
+
else {
|
|
643
|
+
let ll = 0, n = 0;
|
|
644
|
+
if(lit==16) {
|
|
645
|
+
n = (3 + bitsE(data, pos, 2)); pos += 2; ll = tree[i-1];
|
|
646
|
+
}
|
|
647
|
+
else if(lit==17) {
|
|
648
|
+
n = (3 + bitsE(data, pos, 3)); pos += 3;
|
|
649
|
+
}
|
|
650
|
+
else if(lit==18) {
|
|
651
|
+
n = (11 + bitsE(data, pos, 7)); pos += 7;
|
|
652
|
+
}
|
|
653
|
+
let ni = i+n;
|
|
654
|
+
while(i<ni) { tree[i]=ll; i++; }
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
return pos;
|
|
658
|
+
}
|
|
659
|
+
UZIP.F._copyOut = function(src, off, len, tree) {
|
|
660
|
+
let mx=0, i=0, tl=tree.length>>>1;
|
|
661
|
+
while(i<len) { let v=src[i+off]; tree[(i<<1)]=0; tree[(i<<1)+1]=v; if(v>mx)mx=v; i++; }
|
|
662
|
+
while(i<tl ) { tree[(i<<1)]=0; tree[(i<<1)+1]=0; i++; }
|
|
663
|
+
return mx;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
UZIP.F.makeCodes = function(tree, MAX_BITS) { // code, length
|
|
667
|
+
let U = UZIP.F.U;
|
|
668
|
+
let max_code = tree.length;
|
|
669
|
+
let code, bits, n, i, len;
|
|
670
|
+
|
|
671
|
+
let bl_count = U.bl_count; for(let i=0; i<=MAX_BITS; i++) bl_count[i]=0;
|
|
672
|
+
for(i=1; i<max_code; i+=2) bl_count[tree[i]]++;
|
|
673
|
+
|
|
674
|
+
let next_code = U.next_code; // smallest code for each length
|
|
675
|
+
|
|
676
|
+
code = 0;
|
|
677
|
+
bl_count[0] = 0;
|
|
678
|
+
for (bits = 1; bits <= MAX_BITS; bits++) {
|
|
679
|
+
code = (code + bl_count[bits-1]) << 1;
|
|
680
|
+
next_code[bits] = code;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
for (n = 0; n < max_code; n+=2) {
|
|
684
|
+
len = tree[n+1];
|
|
685
|
+
if (len != 0) {
|
|
686
|
+
tree[n] = next_code[len];
|
|
687
|
+
next_code[len]++;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
UZIP.F.codes2map = function(tree, MAX_BITS, map) {
|
|
692
|
+
let max_code = tree.length;
|
|
693
|
+
let U=UZIP.F.U, r15 = U.rev15;
|
|
694
|
+
for(let i=0; i<max_code; i+=2) if(tree[i+1]!=0) {
|
|
695
|
+
let lit = i>>1;
|
|
696
|
+
let cl = tree[i+1], val = (lit<<4)|cl; // : (0x8000 | (U.of0[lit-257]<<7) | (U.exb[lit-257]<<4) | cl);
|
|
697
|
+
let rest = (MAX_BITS-cl), i0 = tree[i]<<rest, i1 = i0 + (1<<rest);
|
|
698
|
+
//tree[i]=r15[i0]>>>(15-MAX_BITS);
|
|
699
|
+
while(i0!=i1) {
|
|
700
|
+
let p0 = r15[i0]>>>(15-MAX_BITS);
|
|
701
|
+
map[p0]=val; i0++;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
UZIP.F.revCodes = function(tree, MAX_BITS) {
|
|
706
|
+
let r15 = UZIP.F.U.rev15, imb = 15-MAX_BITS;
|
|
707
|
+
for(let i=0; i<tree.length; i+=2) { let i0 = (tree[i]<<(MAX_BITS-tree[i+1])); tree[i] = r15[i0]>>>imb; }
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// used only in deflate
|
|
711
|
+
UZIP.F._putsE= function(dt, pos, val ) { val = val<<(pos&7); let o=(pos>>>3); dt[o]|=val; dt[o+1]|=(val>>>8); }
|
|
712
|
+
UZIP.F._putsF= function(dt, pos, val ) { val = val<<(pos&7); let o=(pos>>>3); dt[o]|=val; dt[o+1]|=(val>>>8); dt[o+2]|=(val>>>16); }
|
|
713
|
+
|
|
714
|
+
UZIP.F._bitsE= function(dt, pos, length) { return ((dt[pos>>>3] | (dt[(pos>>>3)+1]<<8) )>>>(pos&7))&((1<<length)-1); }
|
|
715
|
+
UZIP.F._bitsF= function(dt, pos, length) { return ((dt[pos>>>3] | (dt[(pos>>>3)+1]<<8) | (dt[(pos>>>3)+2]<<16))>>>(pos&7))&((1<<length)-1); }
|
|
716
|
+
/*
|
|
717
|
+
UZIP.F._get9 = function(dt, pos) {
|
|
718
|
+
return ((dt[pos>>>3] | (dt[(pos>>>3)+1]<<8))>>>(pos&7))&511;
|
|
719
|
+
} */
|
|
720
|
+
UZIP.F._get17= function(dt, pos) { // return at least 17 meaningful bytes
|
|
721
|
+
return (dt[pos>>>3] | (dt[(pos>>>3)+1]<<8) | (dt[(pos>>>3)+2]<<16) )>>>(pos&7);
|
|
722
|
+
}
|
|
723
|
+
UZIP.F._get25= function(dt, pos) { // return at least 17 meaningful bytes
|
|
724
|
+
return (dt[pos>>>3] | (dt[(pos>>>3)+1]<<8) | (dt[(pos>>>3)+2]<<16) | (dt[(pos>>>3)+3]<<24) )>>>(pos&7);
|
|
725
|
+
}
|
|
726
|
+
UZIP.F.U = function(){
|
|
727
|
+
let u16=Uint16Array, u32=Uint32Array;
|
|
728
|
+
return {
|
|
729
|
+
next_code : new u16(16),
|
|
730
|
+
bl_count : new u16(16),
|
|
731
|
+
ordr : [ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 ],
|
|
732
|
+
of0 : [3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,999,999,999],
|
|
733
|
+
exb : [0,0,0,0,0,0,0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0, 0],
|
|
734
|
+
ldef : new u16(32),
|
|
735
|
+
df0 : [1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577, 65535, 65535],
|
|
736
|
+
dxb : [0,0,0,0,1,1,2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 0, 0],
|
|
737
|
+
ddef : new u32(32),
|
|
738
|
+
flmap: new u16( 512), fltree: [],
|
|
739
|
+
fdmap: new u16( 32), fdtree: [],
|
|
740
|
+
lmap : new u16(32768), ltree : [], ttree:[],
|
|
741
|
+
dmap : new u16(32768), dtree : [],
|
|
742
|
+
imap : new u16( 512), itree : [],
|
|
743
|
+
//rev9 : new u16( 512)
|
|
744
|
+
rev15: new u16(1<<15),
|
|
745
|
+
lhst : new u32(286), dhst : new u32( 30), ihst : new u32(19),
|
|
746
|
+
lits : new u32(15000),
|
|
747
|
+
strt : new u16(1<<16),
|
|
748
|
+
prev : new u16(1<<15)
|
|
749
|
+
};
|
|
750
|
+
} ();
|
|
751
|
+
|
|
752
|
+
(function(){
|
|
753
|
+
let U = UZIP.F.U;
|
|
754
|
+
let len = 1<<15;
|
|
755
|
+
for(let i=0; i<len; i++) {
|
|
756
|
+
let x = i;
|
|
757
|
+
x = (((x & 0xaaaaaaaa) >>> 1) | ((x & 0x55555555) << 1));
|
|
758
|
+
x = (((x & 0xcccccccc) >>> 2) | ((x & 0x33333333) << 2));
|
|
759
|
+
x = (((x & 0xf0f0f0f0) >>> 4) | ((x & 0x0f0f0f0f) << 4));
|
|
760
|
+
x = (((x & 0xff00ff00) >>> 8) | ((x & 0x00ff00ff) << 8));
|
|
761
|
+
U.rev15[i] = (((x >>> 16) | (x << 16)))>>>17;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
function pushV(tgt, n, sv) { while(n--!=0) tgt.push(0,sv); }
|
|
765
|
+
|
|
766
|
+
for(let i=0; i<32; i++) { U.ldef[i]=(U.of0[i]<<3)|U.exb[i]; U.ddef[i]=(U.df0[i]<<4)|U.dxb[i]; }
|
|
767
|
+
|
|
768
|
+
pushV(U.fltree, 144, 8); pushV(U.fltree, 255-143, 9); pushV(U.fltree, 279-255, 7); pushV(U.fltree,287-279,8);
|
|
769
|
+
/*
|
|
770
|
+
let i = 0;
|
|
771
|
+
for(; i<=143; i++) U.fltree.push(0,8);
|
|
772
|
+
for(; i<=255; i++) U.fltree.push(0,9);
|
|
773
|
+
for(; i<=279; i++) U.fltree.push(0,7);
|
|
774
|
+
for(; i<=287; i++) U.fltree.push(0,8);
|
|
775
|
+
*/
|
|
776
|
+
UZIP.F.makeCodes(U.fltree, 9);
|
|
777
|
+
UZIP.F.codes2map(U.fltree, 9, U.flmap);
|
|
778
|
+
UZIP.F.revCodes (U.fltree, 9)
|
|
779
|
+
|
|
780
|
+
pushV(U.fdtree,32,5);
|
|
781
|
+
//for(i=0;i<32; i++) U.fdtree.push(0,5);
|
|
782
|
+
UZIP.F.makeCodes(U.fdtree, 5);
|
|
783
|
+
UZIP.F.codes2map(U.fdtree, 5, U.fdmap);
|
|
784
|
+
UZIP.F.revCodes (U.fdtree, 5)
|
|
785
|
+
|
|
786
|
+
pushV(U.itree,19,0); pushV(U.ltree,286,0); pushV(U.dtree,30,0); pushV(U.ttree,320,0);
|
|
787
|
+
/*
|
|
788
|
+
for(let i=0; i< 19; i++) U.itree.push(0,0);
|
|
789
|
+
for(let i=0; i<286; i++) U.ltree.push(0,0);
|
|
790
|
+
for(let i=0; i< 30; i++) U.dtree.push(0,0);
|
|
791
|
+
for(let i=0; i<320; i++) U.ttree.push(0,0);
|
|
792
|
+
*/
|
|
793
|
+
})();
|
|
794
|
+
|
|
795
|
+
|
|
796
|
+
|
|
797
|
+
let started = false;
|
|
798
|
+
let cloudFrontURL = "";
|
|
799
|
+
let folderName = "";
|
|
800
|
+
let lastRequest = {};//{tc:"00_00_00_00", path:"..."}
|
|
801
|
+
let lastTimeCodeFound = null;//for statistics
|
|
802
|
+
let msecAvg = [];//for statistics
|
|
803
|
+
const delimiter = "_";
|
|
804
|
+
let maxTimeCode = {};//don't pass this timecode
|
|
805
|
+
let enabled = true;
|
|
806
|
+
let isWorking = false;
|
|
807
|
+
|
|
808
|
+
const HandleData = async (data) => {
|
|
809
|
+
const result = UZIP.parse(data);
|
|
810
|
+
let sortedKeys = Object.keys(result).sort();
|
|
811
|
+
|
|
812
|
+
for (const key of sortedKeys) {
|
|
813
|
+
if (key.endsWith(".json")) {
|
|
814
|
+
let transferableArray = [];
|
|
815
|
+
const metadata = JSON.parse(new TextDecoder().decode(result[key]));
|
|
816
|
+
const tc = metadata.timecode.replace(/:/g,"_");//replaceAll(":", "_");
|
|
817
|
+
if (metadata.placeholders) {
|
|
818
|
+
for (const groupId in metadata.placeholders) {
|
|
819
|
+
let alphaKey = \`\${tc}_\${groupId}.alpha\`;
|
|
820
|
+
if (result[alphaKey]) {
|
|
821
|
+
metadata.placeholders[groupId].alphaAsUint8Array = result[alphaKey];//new Uint8Array(result[alphaKey].buffer);
|
|
822
|
+
transferableArray.push(result[alphaKey].buffer);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
postMessage(metadata,transferableArray);
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
const Str2TimeCode = (str) => {
|
|
832
|
+
const list = str.split(delimiter);
|
|
833
|
+
const timecode = {h: parseInt(list[0]), m: parseInt(list[1]), s: parseInt(list[2]), f: parseInt(list[3])};
|
|
834
|
+
return timecode;
|
|
835
|
+
}
|
|
836
|
+
const TimeCode2Str = (timecode) => {
|
|
837
|
+
return ("0" + timecode.h).slice(-2) + delimiter + ("0" + timecode.m).slice(-2) + delimiter + ("0" + timecode.s).slice(-2) + delimiter + ("0" + timecode.f).slice(-2);
|
|
838
|
+
}
|
|
839
|
+
const AddSecondsToTimeCode = (timecode, seconds) => {
|
|
840
|
+
let ret = {...timecode};//cloning araay
|
|
841
|
+
ret.s += seconds;
|
|
842
|
+
if (ret.s >= 60) {
|
|
843
|
+
ret.s = 0;
|
|
844
|
+
ret.m++;
|
|
845
|
+
if (ret.m >= 60) {
|
|
846
|
+
ret.m = 0;
|
|
847
|
+
ret.h++;
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
return ret;
|
|
851
|
+
}
|
|
852
|
+
const GetNextTimeCode_TC = (tc, seconds = 0) => {
|
|
853
|
+
let timecode = Str2TimeCode(tc);
|
|
854
|
+
if (seconds) timecode = AddSecondsToTimeCode(timecode, seconds);
|
|
855
|
+
return timecode;
|
|
856
|
+
}
|
|
857
|
+
const GetNextTimeCode = (tc, seconds = 0) => {
|
|
858
|
+
let timecode = GetNextTimeCode_TC(tc,seconds);
|
|
859
|
+
return TimeCode2Str(timecode);
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
|
|
863
|
+
const IsTCBigger = (tc1, tc2) => {
|
|
864
|
+
//return true iif tc1 > tc2
|
|
865
|
+
if (tc1.h > tc2.h) return true;
|
|
866
|
+
else if (tc1.h < tc2.h) return false;
|
|
867
|
+
else if (tc1.m > tc2.m) return true;
|
|
868
|
+
else if (tc1.m < tc2.m) return false;
|
|
869
|
+
else if (tc1.s > tc2.s) return true;
|
|
870
|
+
else if (tc1.s < tc2.s) return false;
|
|
871
|
+
else if (tc1.f > tc2.f) return true;
|
|
872
|
+
else return false;
|
|
873
|
+
}
|
|
874
|
+
const TryNextFrame = (seconds) => {
|
|
875
|
+
if(!enabled) return;
|
|
876
|
+
//if the required time code exceed the max time code, wait a bit
|
|
877
|
+
if (seconds > 0 && IsTCBigger(GetNextTimeCode_TC(lastRequest.tc, seconds), maxTimeCode)) {
|
|
878
|
+
//console.log("the current TC ", GetNextTimeCode_TC(lastRequest.tc, seconds), " is bigger than the max time code: ", maxTimeCode);//@oror
|
|
879
|
+
setTimeout(() => {
|
|
880
|
+
TryNextFrame(seconds);
|
|
881
|
+
}, 100);
|
|
882
|
+
}
|
|
883
|
+
else
|
|
884
|
+
{
|
|
885
|
+
//console.log("We're good to take the next timecode");//@oror
|
|
886
|
+
setTimeout(() => {
|
|
887
|
+
lastRequest.tc = GetNextTimeCode(lastRequest.tc, seconds);
|
|
888
|
+
//console.log("next time code: ", lastRequest.tc);//@oror
|
|
889
|
+
GetMetaData();
|
|
890
|
+
}, 0);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
const AvgMetaDataMsec = () => {
|
|
895
|
+
let sum = 0;
|
|
896
|
+
for (let i = 0; i < msecAvg.length; i++) {
|
|
897
|
+
sum += msecAvg[i];
|
|
898
|
+
}
|
|
899
|
+
return sum / msecAvg.length;
|
|
900
|
+
};
|
|
901
|
+
const GetMetaData = async () => {
|
|
902
|
+
isWorking = true;
|
|
903
|
+
const item = lastRequest;
|
|
904
|
+
const url = \`\${item.path}/\${item.tc}.zip\`;
|
|
905
|
+
try {
|
|
906
|
+
const t1 = performance.now();
|
|
907
|
+
const res = await fetch(url);
|
|
908
|
+
if (res.status >= 400) {
|
|
909
|
+
//console.log(\`\${item.tc} time code not found, url is \${url}\`);
|
|
910
|
+
let now = performance.now();
|
|
911
|
+
let msecSinceLastTimeDate = now - lastTimeCodeFound;
|
|
912
|
+
let secSinceLastTimeDate = (msecSinceLastTimeDate/1000);
|
|
913
|
+
if (secSinceLastTimeDate > 4) {
|
|
914
|
+
console.error("Didn't find timecode ", item.tc, ", now for " + secSinceLastTimeDate.toFixed(2) + " seconds - give up !");
|
|
915
|
+
TryNextFrame(2);//try the same time code again
|
|
916
|
+
}
|
|
917
|
+
else {
|
|
918
|
+
console.error("Didn't find timecode ", item.tc, ", now for " + secSinceLastTimeDate.toFixed(2) + " seconds");
|
|
919
|
+
TryNextFrame(0);//try the same time code again
|
|
920
|
+
}
|
|
921
|
+
isWorking = false;
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
let now = performance.now();
|
|
925
|
+
let msecSinceLastTimeDate = 0;
|
|
926
|
+
if (lastTimeCodeFound) {
|
|
927
|
+
msecSinceLastTimeDate = now - lastTimeCodeFound;
|
|
928
|
+
msecAvg.push(msecSinceLastTimeDate);
|
|
929
|
+
while (msecAvg.length > 1000) msecAvg.shift();
|
|
930
|
+
}
|
|
931
|
+
lastTimeCodeFound = now;
|
|
932
|
+
|
|
933
|
+
//console.log(\`\${item.tc} time code found, (\${msecSinceLastTimeDate} msec from last time, avg: \${AvgMetaDataMsec()})\`);
|
|
934
|
+
|
|
935
|
+
const data = await res.arrayBuffer();
|
|
936
|
+
await HandleData(data);
|
|
937
|
+
const t2 = performance.now();
|
|
938
|
+
//postMessage({time: \`Fetch and Decompress: \${t2 - t1} msec\`});
|
|
939
|
+
} catch (err) {
|
|
940
|
+
console.error(\`Error on url \${url}\`, err);
|
|
941
|
+
}
|
|
942
|
+
isWorking = false;
|
|
943
|
+
TryNextFrame(2);
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
addEventListener("message", (request) => {
|
|
947
|
+
if (!started && request.data.cloudFrontURL) {
|
|
948
|
+
started = true;
|
|
949
|
+
cloudFrontURL = request.data.cloudFrontURL;
|
|
950
|
+
folderName = request.data.folderName;
|
|
951
|
+
let tcReplaced = request.data.tc.replace(/:/g,"_");//replaceAll(":", "_")
|
|
952
|
+
lastRequest = {tc: tcReplaced, path: \`\${cloudFrontURL}/\${folderName}\`};//from the lastReqest object
|
|
953
|
+
let timeCode = Str2TimeCode(lastRequest.tc);//parse the time code string into a real time-code
|
|
954
|
+
maxTimeCode = AddSecondsToTimeCode(Object.assign({}, timeCode), 4);//set the max limit (on a clone)
|
|
955
|
+
//set the timecode needed
|
|
956
|
+
timeCode.f = 0;
|
|
957
|
+
timeCode.s = timeCode.s % 2 === 0 ? timeCode.s : timeCode.s - 1;
|
|
958
|
+
lastRequest.tc = TimeCode2Str(timeCode);
|
|
959
|
+
console.log("Meta-data worker starting from this time-code: ", lastRequest.tc);//@oror
|
|
960
|
+
GetMetaData();
|
|
961
|
+
}
|
|
962
|
+
else {
|
|
963
|
+
let tcReplaced = request.data.tc.replace(/:/g,"_");
|
|
964
|
+
//console.log("metaDataDownloader new timecode = " + tcReplaced);
|
|
965
|
+
let timeCode = Str2TimeCode(tcReplaced);
|
|
966
|
+
timeCode.f = 0;
|
|
967
|
+
timeCode.s = timeCode.s % 2 === 0 ? timeCode.s : timeCode.s - 1;
|
|
968
|
+
if (IsTCBigger(timeCode, maxTimeCode))
|
|
969
|
+
{
|
|
970
|
+
lastRequest.tc = TimeCode2Str(timeCode);
|
|
971
|
+
console.log("Meta-data worker jumps forward to this time-code: ", lastRequest.tc);
|
|
972
|
+
}
|
|
973
|
+
if (IsTCBigger(Str2TimeCode(lastRequest.tc), AddSecondsToTimeCode(timeCode,4)))
|
|
974
|
+
{
|
|
975
|
+
lastRequest.tc = TimeCode2Str(timeCode);
|
|
976
|
+
console.log("Meta-data worker jumps back to this time-code: ", lastRequest.tc);
|
|
977
|
+
}
|
|
978
|
+
maxTimeCode = AddSecondsToTimeCode(timeCode, 4);
|
|
979
|
+
if(!enabled && request.data.enabled)
|
|
980
|
+
{
|
|
981
|
+
enabled = request.data.enabled;
|
|
982
|
+
if(!isWorking) GetMetaData();
|
|
983
|
+
}
|
|
984
|
+
else {
|
|
985
|
+
enabled = request.data.enabled;
|
|
986
|
+
}
|
|
987
|
+
//console.log("max time code: ", maxTimeCode);
|
|
988
|
+
}
|
|
989
|
+
});
|
|
990
|
+
|
|
991
|
+
} catch (e){
|
|
992
|
+
console.error(e);
|
|
993
|
+
}`;
|
|
994
|
+
|
|
995
|
+
/*
|
|
996
|
+
* Copyright 2021, GFXFundamentals.
|
|
997
|
+
* All rights reserved.
|
|
998
|
+
*
|
|
999
|
+
* Redistribution and use in source and binary forms, with or without
|
|
1000
|
+
* modification, are permitted provided that the following conditions are
|
|
1001
|
+
* met:
|
|
1002
|
+
*
|
|
1003
|
+
* * Redistributions of source code must retain the above copyright
|
|
1004
|
+
* notice, this list of conditions and the following disclaimer.
|
|
1005
|
+
* * Redistributions in binary form must reproduce the above
|
|
1006
|
+
* copyright notice, this list of conditions and the following disclaimer
|
|
1007
|
+
* in the documentation and/or other materials provided with the
|
|
1008
|
+
* distribution.
|
|
1009
|
+
* * Neither the name of GFXFundamentals. nor the names of his
|
|
1010
|
+
* contributors may be used to endorse or promote products derived from
|
|
1011
|
+
* this software without specific prior written permission.
|
|
1012
|
+
*
|
|
1013
|
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
1014
|
+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
1015
|
+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
1016
|
+
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
1017
|
+
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
1018
|
+
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
1019
|
+
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
1020
|
+
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
1021
|
+
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
1022
|
+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
1023
|
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
1024
|
+
*/
|
|
1025
|
+
|
|
1026
|
+
|
|
1027
|
+
let debug_lastFrameFromVideoCallback=-1;
|
|
1028
|
+
let debug_tc = "";
|
|
1029
|
+
|
|
1030
|
+
/* global define */
|
|
1031
|
+
(function(root, factory) { // eslint-disable-line
|
|
1032
|
+
if (typeof define === 'function' && define.amd) {
|
|
1033
|
+
// AMD. Register as an anonymous module.
|
|
1034
|
+
define([], function() {
|
|
1035
|
+
return factory.call(root);
|
|
1036
|
+
});
|
|
1037
|
+
} else {
|
|
1038
|
+
// Browser globals
|
|
1039
|
+
root.webglUtils = factory.call(root);
|
|
1040
|
+
}
|
|
1041
|
+
}(this, function() {
|
|
1042
|
+
"use strict";
|
|
1043
|
+
|
|
1044
|
+
const topWindow = this;
|
|
1045
|
+
|
|
1046
|
+
/** @module webgl-utils */
|
|
1047
|
+
|
|
1048
|
+
function isInIFrame(w) {
|
|
1049
|
+
w = w || topWindow;
|
|
1050
|
+
return w !== w.top;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
if (!isInIFrame()) {
|
|
1054
|
+
console.log("%c%s", 'color:blue;font-weight:bold;', 'for more about webgl-utils.js see:'); // eslint-disable-line
|
|
1055
|
+
console.log("%c%s", 'color:blue;font-weight:bold;', 'http://webgl2fundamentals.org/webgl/lessons/webgl-boilerplate.html'); // eslint-disable-line
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
/**
|
|
1059
|
+
* Wrapped logging function.
|
|
1060
|
+
* @param {string} msg The message to log.
|
|
1061
|
+
*/
|
|
1062
|
+
function error(msg) {
|
|
1063
|
+
if (topWindow.console) {
|
|
1064
|
+
if (topWindow.console.error) {
|
|
1065
|
+
topWindow.console.error(msg);
|
|
1066
|
+
} else if (topWindow.console.log) {
|
|
1067
|
+
topWindow.console.log(msg);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
const errorRE = /ERROR:\s*\d+:(\d+)/gi;
|
|
1073
|
+
function addLineNumbersWithError(src, log = '') {
|
|
1074
|
+
// Note: Error message formats are not defined by any spec so this may or may not work.
|
|
1075
|
+
const matches = [...log.matchAll(errorRE)];
|
|
1076
|
+
const lineNoToErrorMap = new Map(matches.map((m, ndx) => {
|
|
1077
|
+
const lineNo = parseInt(m[1]);
|
|
1078
|
+
const next = matches[ndx + 1];
|
|
1079
|
+
const end = next ? next.index : log.length;
|
|
1080
|
+
const msg = log.substring(m.index, end);
|
|
1081
|
+
return [lineNo - 1, msg];
|
|
1082
|
+
}));
|
|
1083
|
+
return src.split('\n').map((line, lineNo) => {
|
|
1084
|
+
const err = lineNoToErrorMap.get(lineNo);
|
|
1085
|
+
return `${lineNo + 1}: ${line}${err ? `\n\n^^^ ${err}` : ''}`;
|
|
1086
|
+
}).join('\n');
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
|
|
1090
|
+
/**
|
|
1091
|
+
* Error Callback
|
|
1092
|
+
* @callback ErrorCallback
|
|
1093
|
+
* @param {string} msg error message.
|
|
1094
|
+
* @memberOf module:webgl-utils
|
|
1095
|
+
*/
|
|
1096
|
+
|
|
1097
|
+
/**
|
|
1098
|
+
* Loads a shader.
|
|
1099
|
+
* @param {WebGLRenderingContext} gl The WebGLRenderingContext to use.
|
|
1100
|
+
* @param {string} shaderSource The shader source.
|
|
1101
|
+
* @param {number} shaderType The type of shader.
|
|
1102
|
+
* @param {module:webgl-utils.ErrorCallback} opt_errorCallback callback for errors.
|
|
1103
|
+
* @return {WebGLShader} The created shader.
|
|
1104
|
+
*/
|
|
1105
|
+
function loadShader(gl, shaderSource, shaderType, opt_errorCallback) {
|
|
1106
|
+
const errFn = opt_errorCallback || error;
|
|
1107
|
+
// Create the shader object
|
|
1108
|
+
const shader = gl.createShader(shaderType);
|
|
1109
|
+
|
|
1110
|
+
// Load the shader source
|
|
1111
|
+
gl.shaderSource(shader, shaderSource);
|
|
1112
|
+
|
|
1113
|
+
// Compile the shader
|
|
1114
|
+
gl.compileShader(shader);
|
|
1115
|
+
|
|
1116
|
+
// Check the compile status
|
|
1117
|
+
const compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
|
|
1118
|
+
if (!compiled) {
|
|
1119
|
+
// Something went wrong during compilation; get the error
|
|
1120
|
+
const lastError = gl.getShaderInfoLog(shader);
|
|
1121
|
+
errFn(`Error compiling shader: ${lastError}\n${addLineNumbersWithError(shaderSource, lastError)}`);
|
|
1122
|
+
gl.deleteShader(shader);
|
|
1123
|
+
return null;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
return shader;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
/**
|
|
1130
|
+
* Creates a program, attaches shaders, binds attrib locations, links the
|
|
1131
|
+
* program and calls useProgram.
|
|
1132
|
+
* @param {WebGLShader[]} shaders The shaders to attach
|
|
1133
|
+
* @param {string[]} [opt_attribs] An array of attribs names. Locations will be assigned by index if not passed in
|
|
1134
|
+
* @param {number[]} [opt_locations] The locations for the. A parallel array to opt_attribs letting you assign locations.
|
|
1135
|
+
* @param {module:webgl-utils.ErrorCallback} opt_errorCallback callback for errors. By default it just prints an error to the console
|
|
1136
|
+
* on error. If you want something else pass an callback. It's passed an error message.
|
|
1137
|
+
* @memberOf module:webgl-utils
|
|
1138
|
+
*/
|
|
1139
|
+
function createProgram(
|
|
1140
|
+
gl, shaders, opt_attribs, opt_locations, opt_errorCallback) {
|
|
1141
|
+
const errFn = opt_errorCallback || error;
|
|
1142
|
+
const program = gl.createProgram();
|
|
1143
|
+
shaders.forEach(function(shader) {
|
|
1144
|
+
gl.attachShader(program, shader);
|
|
1145
|
+
});
|
|
1146
|
+
if (opt_attribs) {
|
|
1147
|
+
opt_attribs.forEach(function(attrib, ndx) {
|
|
1148
|
+
gl.bindAttribLocation(
|
|
1149
|
+
program,
|
|
1150
|
+
opt_locations ? opt_locations[ndx] : ndx,
|
|
1151
|
+
attrib);
|
|
1152
|
+
});
|
|
1153
|
+
}
|
|
1154
|
+
gl.linkProgram(program);
|
|
1155
|
+
|
|
1156
|
+
// Check the link status
|
|
1157
|
+
const linked = gl.getProgramParameter(program, gl.LINK_STATUS);
|
|
1158
|
+
if (!linked) {
|
|
1159
|
+
// something went wrong with the link
|
|
1160
|
+
const lastError = gl.getProgramInfoLog(program);
|
|
1161
|
+
errFn(`Error in program linking: ${lastError}\n${
|
|
1162
|
+
shaders.map(shader => {
|
|
1163
|
+
const src = addLineNumbersWithError(gl.getShaderSource(shader));
|
|
1164
|
+
const type = gl.getShaderParameter(shader, gl.SHADER_TYPE);
|
|
1165
|
+
return `${glEnumToString(gl, type)}:\n${src}`;
|
|
1166
|
+
}).join('\n')
|
|
1167
|
+
}`);
|
|
1168
|
+
|
|
1169
|
+
gl.deleteProgram(program);
|
|
1170
|
+
return null;
|
|
1171
|
+
}
|
|
1172
|
+
return program;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
/**
|
|
1176
|
+
* Loads a shader from a script tag.
|
|
1177
|
+
* @param {WebGLRenderingContext} gl The WebGLRenderingContext to use.
|
|
1178
|
+
* @param {string} scriptId The id of the script tag.
|
|
1179
|
+
* @param {number} opt_shaderType The type of shader. If not passed in it will
|
|
1180
|
+
* be derived from the type of the script tag.
|
|
1181
|
+
* @param {module:webgl-utils.ErrorCallback} opt_errorCallback callback for errors.
|
|
1182
|
+
* @return {WebGLShader} The created shader.
|
|
1183
|
+
*/
|
|
1184
|
+
function createShaderFromScript(
|
|
1185
|
+
gl, scriptId, opt_shaderType, opt_errorCallback) {
|
|
1186
|
+
let shaderSource = "";
|
|
1187
|
+
let shaderType;
|
|
1188
|
+
const shaderScript = document.getElementById(scriptId);
|
|
1189
|
+
if (!shaderScript) {
|
|
1190
|
+
throw ("*** Error: unknown script element" + scriptId);
|
|
1191
|
+
}
|
|
1192
|
+
shaderSource = shaderScript.text;
|
|
1193
|
+
|
|
1194
|
+
if (!opt_shaderType) {
|
|
1195
|
+
if (shaderScript.type === "x-shader/x-vertex") {
|
|
1196
|
+
shaderType = gl.VERTEX_SHADER;
|
|
1197
|
+
} else if (shaderScript.type === "x-shader/x-fragment") {
|
|
1198
|
+
shaderType = gl.FRAGMENT_SHADER;
|
|
1199
|
+
} else if (shaderType !== gl.VERTEX_SHADER && shaderType !== gl.FRAGMENT_SHADER) {
|
|
1200
|
+
throw ("*** Error: unknown shader type");
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
return loadShader(
|
|
1205
|
+
gl, shaderSource, opt_shaderType ? opt_shaderType : shaderType,
|
|
1206
|
+
opt_errorCallback);
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
const defaultShaderType = [
|
|
1210
|
+
"VERTEX_SHADER",
|
|
1211
|
+
"FRAGMENT_SHADER",
|
|
1212
|
+
];
|
|
1213
|
+
|
|
1214
|
+
/**
|
|
1215
|
+
* Creates a program from 2 script tags.
|
|
1216
|
+
*
|
|
1217
|
+
* @param {WebGLRenderingContext} gl The WebGLRenderingContext
|
|
1218
|
+
* to use.
|
|
1219
|
+
* @param {string[]} shaderScriptIds Array of ids of the script
|
|
1220
|
+
* tags for the shaders. The first is assumed to be the
|
|
1221
|
+
* vertex shader, the second the fragment shader.
|
|
1222
|
+
* @param {string[]} [opt_attribs] An array of attribs names. Locations will be assigned by index if not passed in
|
|
1223
|
+
* @param {number[]} [opt_locations] The locations for the. A parallel array to opt_attribs letting you assign locations.
|
|
1224
|
+
* @param {module:webgl-utils.ErrorCallback} opt_errorCallback callback for errors. By default it just prints an error to the console
|
|
1225
|
+
* on error. If you want something else pass an callback. It's passed an error message.
|
|
1226
|
+
* @return {WebGLProgram} The created program.
|
|
1227
|
+
* @memberOf module:webgl-utils
|
|
1228
|
+
*/
|
|
1229
|
+
function createProgramFromScripts(
|
|
1230
|
+
gl, shaderScriptIds, opt_attribs, opt_locations, opt_errorCallback) {
|
|
1231
|
+
const shaders = [];
|
|
1232
|
+
for (let ii = 0; ii < shaderScriptIds.length; ++ii) {
|
|
1233
|
+
shaders.push(createShaderFromScript(
|
|
1234
|
+
gl, shaderScriptIds[ii], gl[defaultShaderType[ii]], opt_errorCallback));
|
|
1235
|
+
}
|
|
1236
|
+
return createProgram(gl, shaders, opt_attribs, opt_locations, opt_errorCallback);
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
/**
|
|
1240
|
+
* Creates a program from 2 sources.
|
|
1241
|
+
*
|
|
1242
|
+
* @param {WebGLRenderingContext} gl The WebGLRenderingContext
|
|
1243
|
+
* to use.
|
|
1244
|
+
* @param {string[]} shaderSourcess Array of sources for the
|
|
1245
|
+
* shaders. The first is assumed to be the vertex shader,
|
|
1246
|
+
* the second the fragment shader.
|
|
1247
|
+
* @param {string[]} [opt_attribs] An array of attribs names. Locations will be assigned by index if not passed in
|
|
1248
|
+
* @param {number[]} [opt_locations] The locations for the. A parallel array to opt_attribs letting you assign locations.
|
|
1249
|
+
* @param {module:webgl-utils.ErrorCallback} opt_errorCallback callback for errors. By default it just prints an error to the console
|
|
1250
|
+
* on error. If you want something else pass an callback. It's passed an error message.
|
|
1251
|
+
* @return {WebGLProgram} The created program.
|
|
1252
|
+
* @memberOf module:webgl-utils
|
|
1253
|
+
*/
|
|
1254
|
+
function createProgramFromSources(
|
|
1255
|
+
gl, shaderSources, opt_attribs, opt_locations, opt_errorCallback) {
|
|
1256
|
+
const shaders = [];
|
|
1257
|
+
for (let ii = 0; ii < shaderSources.length; ++ii) {
|
|
1258
|
+
shaders.push(loadShader(
|
|
1259
|
+
gl, shaderSources[ii], gl[defaultShaderType[ii]], opt_errorCallback));
|
|
1260
|
+
}
|
|
1261
|
+
return createProgram(gl, shaders, opt_attribs, opt_locations, opt_errorCallback);
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
/**
|
|
1265
|
+
* Resize a canvas to match the size its displayed.
|
|
1266
|
+
* @param {HTMLCanvasElement} canvas The canvas to resize.
|
|
1267
|
+
* @param {number} [multiplier] amount to multiply by.
|
|
1268
|
+
* Pass in window.devicePixelRatio for native pixels.
|
|
1269
|
+
* @return {boolean} true if the canvas was resized.
|
|
1270
|
+
* @memberOf module:webgl-utils
|
|
1271
|
+
*/
|
|
1272
|
+
function resizeCanvasToDisplaySize(canvas, multiplier) {
|
|
1273
|
+
multiplier = multiplier || 1;
|
|
1274
|
+
const width = canvas.clientWidth * multiplier | 0;
|
|
1275
|
+
const height = canvas.clientHeight * multiplier | 0;
|
|
1276
|
+
if (canvas.width !== width || canvas.height !== height) {
|
|
1277
|
+
canvas.width = width;
|
|
1278
|
+
canvas.height = height;
|
|
1279
|
+
return true;
|
|
1280
|
+
}
|
|
1281
|
+
return false;
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
return {
|
|
1285
|
+
createProgram: createProgram,
|
|
1286
|
+
createProgramFromScripts: createProgramFromScripts,
|
|
1287
|
+
createProgramFromSources: createProgramFromSources,
|
|
1288
|
+
resizeCanvasToDisplaySize: resizeCanvasToDisplaySize,
|
|
1289
|
+
};
|
|
1290
|
+
|
|
1291
|
+
}));
|
|
1292
|
+
const BB = {MAX_LOGOS:4};
|
|
1293
|
+
BB.GL = {
|
|
1294
|
+
|
|
1295
|
+
vertexShaderSource : `#version 300 es
|
|
1296
|
+
|
|
1297
|
+
// an attribute is an input (in) to a vertex shader.
|
|
1298
|
+
// It will receive vertex data from the buffer
|
|
1299
|
+
in vec2 a_position;
|
|
1300
|
+
|
|
1301
|
+
//Identity matrix for now
|
|
1302
|
+
uniform mat3 u_matrix;
|
|
1303
|
+
|
|
1304
|
+
//The canvas resolution, to covert to ZeTor
|
|
1305
|
+
uniform vec2 u_resolution;
|
|
1306
|
+
|
|
1307
|
+
//a_texCoord->v_texCoord interpolation
|
|
1308
|
+
//in vec2 a_texCoord;
|
|
1309
|
+
//out vec2 v_texCoord;
|
|
1310
|
+
|
|
1311
|
+
// Main vertex shader
|
|
1312
|
+
void main() {
|
|
1313
|
+
//Translate, rotate, scale by u_matrix:
|
|
1314
|
+
vec2 position = (u_matrix * vec3(a_position, 1)).xy;
|
|
1315
|
+
|
|
1316
|
+
//Convert the position from pixels to ZeTor [0.0,1.0]
|
|
1317
|
+
vec2 zeroToOne = position / u_resolution;
|
|
1318
|
+
|
|
1319
|
+
//Convert from [0,1] to [0,2]
|
|
1320
|
+
vec2 zeroToTwo = zeroToOne * 2.0;
|
|
1321
|
+
|
|
1322
|
+
//Convert from [0,2] to [-1,+1] (clipspace)
|
|
1323
|
+
vec2 clipSpace = zeroToTwo - 1.0;
|
|
1324
|
+
|
|
1325
|
+
gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
|
|
1326
|
+
|
|
1327
|
+
//Pass the texCoord to the fragment shader
|
|
1328
|
+
//The GPU will interpolate this value between points.
|
|
1329
|
+
//v_texCoord = a_texCoord;
|
|
1330
|
+
}
|
|
1331
|
+
`,
|
|
1332
|
+
|
|
1333
|
+
fragmentShaderSource : `#version 300 es
|
|
1334
|
+
|
|
1335
|
+
//High precision mode
|
|
1336
|
+
precision highp float;
|
|
1337
|
+
|
|
1338
|
+
//The textures
|
|
1339
|
+
//uniform sampler2D u_image;//video frame's texture
|
|
1340
|
+
uniform sampler2D ad0;//the ad texture
|
|
1341
|
+
uniform sampler2D ad1;//the ad texture
|
|
1342
|
+
uniform sampler2D ad2;//the ad texture
|
|
1343
|
+
uniform sampler2D ad3;//the ad texture
|
|
1344
|
+
|
|
1345
|
+
uniform vec2 u_resolution2;//resolution of the canvas
|
|
1346
|
+
|
|
1347
|
+
//v_texCoord from the vertex shader
|
|
1348
|
+
//in vec2 v_texCoord;
|
|
1349
|
+
|
|
1350
|
+
//Logos' staff
|
|
1351
|
+
uniform int hasLogo0;//has logo ?
|
|
1352
|
+
uniform int adForLogo0;
|
|
1353
|
+
uniform sampler2D alpha0;//the alpha's mate
|
|
1354
|
+
uniform mat3 u_matrix0;//the reverse homography
|
|
1355
|
+
uniform vec2 u_alpha0TopLeft;//the alpha mate's top-left
|
|
1356
|
+
uniform vec2 u_alpha0Size;//the alpha mate's size
|
|
1357
|
+
|
|
1358
|
+
uniform int hasLogo1;//has logo ?
|
|
1359
|
+
uniform int adForLogo1;
|
|
1360
|
+
uniform sampler2D alpha1;//the alpha's mate
|
|
1361
|
+
uniform mat3 u_matrix1;//the reverse homography
|
|
1362
|
+
uniform vec2 u_alpha1TopLeft;//the alpha mate's top-left
|
|
1363
|
+
uniform vec2 u_alpha1Size;//the alpha mate's size
|
|
1364
|
+
|
|
1365
|
+
uniform int hasLogo2;//has logo ?
|
|
1366
|
+
uniform int adForLogo2;
|
|
1367
|
+
uniform sampler2D alpha2;//the alpha's mate
|
|
1368
|
+
uniform mat3 u_matrix2;//the reverse homography
|
|
1369
|
+
uniform vec2 u_alpha2TopLeft;//the alpha mate's top-left
|
|
1370
|
+
uniform vec2 u_alpha2Size;//the alpha mate's size
|
|
1371
|
+
|
|
1372
|
+
uniform int hasLogo3;//has logo ?
|
|
1373
|
+
uniform int adForLogo3;
|
|
1374
|
+
uniform sampler2D alpha3;//the alpha's mate
|
|
1375
|
+
uniform mat3 u_matrix3;//the reverse homography
|
|
1376
|
+
uniform vec2 u_alpha3TopLeft;//the alpha mate's top-left
|
|
1377
|
+
uniform vec2 u_alpha3Size;//the alpha mate's size
|
|
1378
|
+
|
|
1379
|
+
uniform int hasDigit;//has debug digit texture
|
|
1380
|
+
uniform sampler2D digitTex;//the digits's mate
|
|
1381
|
+
uniform vec2 u_digitTopLeft;//the digit mate's top-left
|
|
1382
|
+
uniform vec2 u_digitTopLeft2;//the digit mate's top-left
|
|
1383
|
+
uniform vec2 u_digitSize;//the digit mate's size
|
|
1384
|
+
|
|
1385
|
+
//Output
|
|
1386
|
+
out vec4 outColor;
|
|
1387
|
+
bool BlendDigit(in vec2 curCoord) {
|
|
1388
|
+
//vec2 texCoord = curCoord;
|
|
1389
|
+
//texCoord /= u_resolution2;
|
|
1390
|
+
vec2 digitCoord = (curCoord - u_digitTopLeft)/u_digitSize;//get the coordinate on the digit size
|
|
1391
|
+
if (digitCoord.x >= 0.0 && digitCoord.y >= 0.0 && digitCoord.x <= 1.0 && digitCoord.y <= 1.0)
|
|
1392
|
+
{
|
|
1393
|
+
outColor = texture(digitTex, digitCoord);
|
|
1394
|
+
return true;
|
|
1395
|
+
}
|
|
1396
|
+
return false;
|
|
1397
|
+
}
|
|
1398
|
+
bool BlendDigit2(in vec2 curCoord) {
|
|
1399
|
+
//vec2 texCoord = curCoord;
|
|
1400
|
+
//texCoord /= u_resolution2;
|
|
1401
|
+
vec2 digitCoord = (curCoord - u_digitTopLeft2)/u_digitSize;//get the coordinate on the digit size
|
|
1402
|
+
if (digitCoord.x >= 0.0 && digitCoord.y >= 0.0 && digitCoord.x <= 1.0 && digitCoord.y <= 1.0)
|
|
1403
|
+
{
|
|
1404
|
+
outColor = texture(digitTex, digitCoord);
|
|
1405
|
+
return true;
|
|
1406
|
+
}
|
|
1407
|
+
return false;
|
|
1408
|
+
}
|
|
1409
|
+
bool BlendWithLogo(
|
|
1410
|
+
in int adUnit,
|
|
1411
|
+
in sampler2D alphaTexture,
|
|
1412
|
+
in mat3 matrix,
|
|
1413
|
+
in vec2 curCoord,
|
|
1414
|
+
in vec2 alphaTopLeft,
|
|
1415
|
+
in vec2 alphaSize)
|
|
1416
|
+
{
|
|
1417
|
+
|
|
1418
|
+
|
|
1419
|
+
|
|
1420
|
+
vec2 texCoord;
|
|
1421
|
+
texCoord.x = (matrix[0][0]*curCoord.x + matrix[0][1]*curCoord.y + matrix[0][2]) / (matrix[2][0]*curCoord.x + matrix[2][1]*curCoord.y + 1.0);
|
|
1422
|
+
texCoord.y = (matrix[1][0]*curCoord.x + matrix[1][1]*curCoord.y + matrix[1][2]) / (matrix[2][0]*curCoord.x + matrix[2][1]*curCoord.y + 1.0);
|
|
1423
|
+
texCoord /= u_resolution2;
|
|
1424
|
+
|
|
1425
|
+
//if we're in the bounding rect of the logo
|
|
1426
|
+
if (texCoord.x >= 0.0 && texCoord.y >= 0.0 && texCoord.x <= 1.0 && texCoord.y <= 1.0)
|
|
1427
|
+
{
|
|
1428
|
+
|
|
1429
|
+
|
|
1430
|
+
float alpha = 1.0;
|
|
1431
|
+
vec2 alphaCoord = (curCoord - alphaTopLeft)/alphaSize;//get the coordinate on the alpha mate
|
|
1432
|
+
if (
|
|
1433
|
+
alphaCoord.x >= 0.0 &&
|
|
1434
|
+
alphaCoord.y >= 0.0 &&
|
|
1435
|
+
alphaCoord.x <= 1.0 &&
|
|
1436
|
+
alphaCoord.y <= 1.0
|
|
1437
|
+
)
|
|
1438
|
+
{
|
|
1439
|
+
//get the alpha texel:
|
|
1440
|
+
alpha *= texture(alphaTexture, alphaCoord).a;//without blur
|
|
1441
|
+
}
|
|
1442
|
+
vec4 ad0Pix =
|
|
1443
|
+
adUnit == 0 ? texture(ad0, texCoord) :
|
|
1444
|
+
adUnit == 1 ? texture(ad1, texCoord) :
|
|
1445
|
+
adUnit == 2 ? texture(ad2, texCoord) :
|
|
1446
|
+
texture(ad3, texCoord);//get the logo's texel
|
|
1447
|
+
alpha *= ad0Pix.a;//consider the alpha of the logo
|
|
1448
|
+
vec3 resultRGB = ad0Pix.rgb;// + videoImagePix.rgb*(1.0-alpha);//blend
|
|
1449
|
+
if (alpha == 0.0) resultRGB = vec3(0,0,0);
|
|
1450
|
+
outColor = vec4(resultRGB, alpha);
|
|
1451
|
+
return true;
|
|
1452
|
+
}
|
|
1453
|
+
else return false;
|
|
1454
|
+
|
|
1455
|
+
}
|
|
1456
|
+
void main()
|
|
1457
|
+
{
|
|
1458
|
+
//calculate cur-coord (with pixels units)
|
|
1459
|
+
vec2 curCoord = gl_FragCoord.xy;
|
|
1460
|
+
curCoord.y = u_resolution2[1] - curCoord.y;
|
|
1461
|
+
//if (hasDigit == 1 && (BlendDigit(curCoord) || BlendDigit2(curCoord))) return;
|
|
1462
|
+
if (hasDigit == 1 && (BlendDigit(curCoord))) return;
|
|
1463
|
+
else if (hasLogo0 == 1 &&
|
|
1464
|
+
BlendWithLogo(adForLogo0,
|
|
1465
|
+
alpha0, u_matrix0, curCoord,u_alpha0TopLeft, u_alpha0Size))
|
|
1466
|
+
{
|
|
1467
|
+
return;
|
|
1468
|
+
}
|
|
1469
|
+
else if (hasLogo1 == 1 &&
|
|
1470
|
+
BlendWithLogo(adForLogo1,
|
|
1471
|
+
alpha1, u_matrix1, curCoord,u_alpha1TopLeft, u_alpha1Size))
|
|
1472
|
+
{
|
|
1473
|
+
return;
|
|
1474
|
+
}
|
|
1475
|
+
else if (hasLogo2 == 1 &&
|
|
1476
|
+
BlendWithLogo(adForLogo2,
|
|
1477
|
+
alpha2, u_matrix2, curCoord,u_alpha2TopLeft, u_alpha2Size))
|
|
1478
|
+
{
|
|
1479
|
+
return;
|
|
1480
|
+
}
|
|
1481
|
+
else if (hasLogo3 == 1 &&
|
|
1482
|
+
BlendWithLogo(adForLogo3,
|
|
1483
|
+
alpha3, u_matrix3, curCoord,u_alpha3TopLeft, u_alpha3Size))
|
|
1484
|
+
{
|
|
1485
|
+
return;
|
|
1486
|
+
}
|
|
1487
|
+
else {//no logo
|
|
1488
|
+
outColor = vec4(0,0,0,0);
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
`,
|
|
1492
|
+
|
|
1493
|
+
vertexShaderSourceV1 : `
|
|
1494
|
+
|
|
1495
|
+
//High precision mode
|
|
1496
|
+
precision highp float;
|
|
1497
|
+
|
|
1498
|
+
// an attribute is an input (in) to a vertex shader.
|
|
1499
|
+
// It will receive vertex data from the buffer
|
|
1500
|
+
attribute vec2 a_position;
|
|
1501
|
+
attribute vec2 a_texCoord;
|
|
1502
|
+
uniform vec2 u_resolution;
|
|
1503
|
+
varying vec2 v_texCoord;
|
|
1504
|
+
|
|
1505
|
+
|
|
1506
|
+
// Main vertex shader
|
|
1507
|
+
void main() {
|
|
1508
|
+
// convert the rectangle from pixels to 0.0 to 1.0
|
|
1509
|
+
vec2 zeroToOne = a_position / u_resolution;
|
|
1510
|
+
|
|
1511
|
+
// convert from 0->1 to 0->2
|
|
1512
|
+
vec2 zeroToTwo = zeroToOne * 2.0;
|
|
1513
|
+
|
|
1514
|
+
// convert from 0->2 to -1->+1 (clipspace)
|
|
1515
|
+
vec2 clipSpace = zeroToTwo - 1.0;
|
|
1516
|
+
|
|
1517
|
+
gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
|
|
1518
|
+
|
|
1519
|
+
// pass the texCoord to the fragment shader
|
|
1520
|
+
// The GPU will interpolate this value between points.
|
|
1521
|
+
v_texCoord = a_texCoord;
|
|
1522
|
+
}
|
|
1523
|
+
`,
|
|
1524
|
+
|
|
1525
|
+
fragmentShaderSourceV1 : `
|
|
1526
|
+
|
|
1527
|
+
//High precision mode
|
|
1528
|
+
precision highp float;
|
|
1529
|
+
|
|
1530
|
+
//The textures
|
|
1531
|
+
uniform sampler2D u_image;//video frame's texture2D
|
|
1532
|
+
uniform sampler2D ad0;//the ad texture2D
|
|
1533
|
+
uniform sampler2D ad1;//the ad texture2D
|
|
1534
|
+
uniform sampler2D ad2;//the ad texture2D
|
|
1535
|
+
uniform sampler2D ad3;//the ad texture2D
|
|
1536
|
+
|
|
1537
|
+
uniform vec2 u_resolution2;//resolution of the canvas
|
|
1538
|
+
|
|
1539
|
+
//v_texCoord from the vertex shader
|
|
1540
|
+
varying vec2 v_texCoord;
|
|
1541
|
+
|
|
1542
|
+
//Logos' staff
|
|
1543
|
+
uniform int hasLogo0;//has logo ?
|
|
1544
|
+
uniform int adForLogo0;
|
|
1545
|
+
uniform sampler2D alpha0;//the alpha's mate
|
|
1546
|
+
uniform mat3 u_matrix0;//the reverse homography
|
|
1547
|
+
uniform vec2 u_alpha0TopLeft;//the alpha mate's top-left
|
|
1548
|
+
uniform vec2 u_alpha0Size;//the alpha mate's size
|
|
1549
|
+
|
|
1550
|
+
uniform int hasLogo1;//has logo ?
|
|
1551
|
+
uniform int adForLogo1;
|
|
1552
|
+
uniform sampler2D alpha1;//the alpha's mate
|
|
1553
|
+
uniform mat3 u_matrix1;//the reverse homography
|
|
1554
|
+
uniform vec2 u_alpha1TopLeft;//the alpha mate's top-left
|
|
1555
|
+
uniform vec2 u_alpha1Size;//the alpha mate's size
|
|
1556
|
+
|
|
1557
|
+
uniform int hasLogo2;//has logo ?
|
|
1558
|
+
uniform int adForLogo2;
|
|
1559
|
+
uniform sampler2D alpha2;//the alpha's mate
|
|
1560
|
+
uniform mat3 u_matrix2;//the reverse homography
|
|
1561
|
+
uniform vec2 u_alpha2TopLeft;//the alpha mate's top-left
|
|
1562
|
+
uniform vec2 u_alpha2Size;//the alpha mate's size
|
|
1563
|
+
|
|
1564
|
+
uniform int hasLogo3;//has logo ?
|
|
1565
|
+
uniform int adForLogo3;
|
|
1566
|
+
uniform sampler2D alpha3;//the alpha's mate
|
|
1567
|
+
uniform mat3 u_matrix3;//the reverse homography
|
|
1568
|
+
uniform vec2 u_alpha3TopLeft;//the alpha mate's top-left
|
|
1569
|
+
uniform vec2 u_alpha3Size;//the alpha mate's size
|
|
1570
|
+
|
|
1571
|
+
uniform sampler2D injectoLogo;//the injecto logo to cover the timecode watermark
|
|
1572
|
+
|
|
1573
|
+
const float Directions = 16.0; // BLUR DIRECTIONS (Default 16.0 - More is better but slower)
|
|
1574
|
+
const float Quality = 3.0; // BLUR QUALITY (Default 3.0 - More is better but slower)
|
|
1575
|
+
const float Size = 12.0; // BLUR SIZE (Radius)
|
|
1576
|
+
const float Pi = 6.28318530718; // Pi*2
|
|
1577
|
+
|
|
1578
|
+
vec3 BlurBGR(vec4 videoImagePix)
|
|
1579
|
+
{
|
|
1580
|
+
vec3 res = videoImagePix.rgb;
|
|
1581
|
+
//get the kernel radius:
|
|
1582
|
+
vec2 Radius = Size/u_resolution2.xy;
|
|
1583
|
+
|
|
1584
|
+
// Blur around:
|
|
1585
|
+
for( float d=0.0; d<Pi; d+=Pi/Directions)
|
|
1586
|
+
{
|
|
1587
|
+
for(float i=1.0/Quality; i<=1.0; i+=1.0/Quality)
|
|
1588
|
+
{
|
|
1589
|
+
res += texture2D( u_image, v_texCoord+vec2(cos(d),sin(d))*Radius*i).rgb;
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
res /= Quality * Directions - 15.0;
|
|
1593
|
+
|
|
1594
|
+
return res;
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
bool BlendWithLogo(
|
|
1598
|
+
int adUnit,
|
|
1599
|
+
sampler2D alphaTexture,
|
|
1600
|
+
mat3 matrix,
|
|
1601
|
+
vec2 curCoord,
|
|
1602
|
+
vec2 alphaTopLeft,
|
|
1603
|
+
vec2 alphaSize,
|
|
1604
|
+
vec4 videoImagePix)
|
|
1605
|
+
{
|
|
1606
|
+
vec2 texCoord;
|
|
1607
|
+
texCoord.x = (matrix[0][0]*curCoord.x + matrix[0][1]*curCoord.y + matrix[0][2]) / (matrix[2][0]*curCoord.x + matrix[2][1]*curCoord.y + 1.0);
|
|
1608
|
+
texCoord.y = (matrix[1][0]*curCoord.x + matrix[1][1]*curCoord.y + matrix[1][2]) / (matrix[2][0]*curCoord.x + matrix[2][1]*curCoord.y + 1.0);
|
|
1609
|
+
texCoord /= u_resolution2;
|
|
1610
|
+
|
|
1611
|
+
//if we're in the bounding rect of the logo
|
|
1612
|
+
if (texCoord.x >= 0.0 && texCoord.y >= 0.0 && texCoord.x <= 1.0 && texCoord.y <= 1.0)
|
|
1613
|
+
{
|
|
1614
|
+
|
|
1615
|
+
|
|
1616
|
+
float alpha = 1.0;
|
|
1617
|
+
vec2 alphaCoord = (curCoord - alphaTopLeft)/alphaSize;//get the coordinate on the alpha mate
|
|
1618
|
+
if (alphaCoord.x >= 0.0 && alphaCoord.y >= 0.0 && alphaCoord.x <= 1.0 && alphaCoord.y <= 1.0)
|
|
1619
|
+
{
|
|
1620
|
+
//get the alpha texel:
|
|
1621
|
+
//alpha *= BlurAlpha(alphaCoord);//blurred
|
|
1622
|
+
alpha *= texture2D(alphaTexture, alphaCoord).a;//without blur
|
|
1623
|
+
}
|
|
1624
|
+
if (adUnit == 99)//blur
|
|
1625
|
+
{
|
|
1626
|
+
gl_FragColor = vec4(BlurBGR(videoImagePix)*alpha + videoImagePix.rgb*(1.0-alpha), 1.0);
|
|
1627
|
+
//outColor = vec4(BlurBGR(videoImagePix), 1.0);
|
|
1628
|
+
return true;
|
|
1629
|
+
}
|
|
1630
|
+
vec4 ad0Pix =
|
|
1631
|
+
adUnit == 0 ? texture2D(ad0, texCoord) :
|
|
1632
|
+
adUnit == 1 ? texture2D(ad1, texCoord) :
|
|
1633
|
+
adUnit == 2 ? texture2D(ad2, texCoord) :
|
|
1634
|
+
texture2D(ad3, texCoord);//get the logo's texel
|
|
1635
|
+
alpha *= ad0Pix.a;//consider the alpha of the logo
|
|
1636
|
+
vec3 resultRGB = ad0Pix.rgb*alpha + videoImagePix.rgb*(1.0-alpha);//blend
|
|
1637
|
+
gl_FragColor = vec4(resultRGB, 1.0);
|
|
1638
|
+
return true;
|
|
1639
|
+
}
|
|
1640
|
+
else return false;
|
|
1641
|
+
|
|
1642
|
+
}
|
|
1643
|
+
void main()
|
|
1644
|
+
{
|
|
1645
|
+
//calculate cur-coord (with pixels units)
|
|
1646
|
+
vec2 curCoord = gl_FragCoord.xy;
|
|
1647
|
+
curCoord.y = u_resolution2[1] - curCoord.y;
|
|
1648
|
+
|
|
1649
|
+
//cover the timecode with some watermark
|
|
1650
|
+
if (v_texCoord[0] <= 0.125 && (1.0-v_texCoord[1]) <= 0.125)
|
|
1651
|
+
{
|
|
1652
|
+
gl_FragColor = texture2D(injectoLogo, vec2(v_texCoord[0]*8.0, 1.0-(1.0-v_texCoord[1])*8.0));
|
|
1653
|
+
return;
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
vec4 videoImagePix = texture2D(u_image, v_texCoord);
|
|
1657
|
+
|
|
1658
|
+
if (hasLogo0 == 1 &&
|
|
1659
|
+
BlendWithLogo(
|
|
1660
|
+
adForLogo0,
|
|
1661
|
+
alpha0, u_matrix0, curCoord,u_alpha0TopLeft, u_alpha0Size, videoImagePix))
|
|
1662
|
+
{
|
|
1663
|
+
return;
|
|
1664
|
+
}
|
|
1665
|
+
else if (hasLogo1 == 1 &&
|
|
1666
|
+
BlendWithLogo(
|
|
1667
|
+
adForLogo1,
|
|
1668
|
+
alpha1, u_matrix1, curCoord,u_alpha1TopLeft, u_alpha1Size, videoImagePix))
|
|
1669
|
+
{
|
|
1670
|
+
return;
|
|
1671
|
+
}
|
|
1672
|
+
else if (hasLogo2 == 1 &&
|
|
1673
|
+
BlendWithLogo(
|
|
1674
|
+
adForLogo2,
|
|
1675
|
+
alpha2, u_matrix2, curCoord,u_alpha2TopLeft, u_alpha2Size, videoImagePix))
|
|
1676
|
+
{
|
|
1677
|
+
return;
|
|
1678
|
+
}
|
|
1679
|
+
else if (hasLogo3 == 1 &&
|
|
1680
|
+
BlendWithLogo(
|
|
1681
|
+
adForLogo3,
|
|
1682
|
+
alpha3, u_matrix3, curCoord,u_alpha3TopLeft, u_alpha3Size, videoImagePix))
|
|
1683
|
+
{
|
|
1684
|
+
return;
|
|
1685
|
+
}
|
|
1686
|
+
else {//only video texel, no logo
|
|
1687
|
+
gl_FragColor = videoImagePix;
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
`
|
|
1691
|
+
}
|
|
1692
|
+
BB.FrameRateMode = {
|
|
1693
|
+
Src60_MediaTime60: Symbol('Src60_MediaTime60'),//source is 60, player's fps is 60 as well
|
|
1694
|
+
Src59_MediaTime60: Symbol('Src59_MediaTime60'),//source is 59.94, player's fps is 60 (dropping every 1001 frame)
|
|
1695
|
+
Src59_MediaTime59: Symbol('Src59_MediaTime59'),//source is 59.94, player's fps is 59.94fps as well
|
|
1696
|
+
Src29_MediaTime29: Symbol('Src29_MediaTime29')//source is 29.97, player's fps is 29.97fps as well
|
|
1697
|
+
};
|
|
1698
|
+
BB.RenderingMode = {
|
|
1699
|
+
RenderDigits : Symbol('RenderDigits'),
|
|
1700
|
+
RenderTimecode : Symbol('RenderTimecode'),
|
|
1701
|
+
Augment : Symbol('Augment')
|
|
1702
|
+
};
|
|
1703
|
+
BB.SynchMethod = {
|
|
1704
|
+
OnNewSegment : Symbol('OnNewSegment'),
|
|
1705
|
+
OnMetadata : Symbol('OnMetadata'),
|
|
1706
|
+
None : Symbol('None'),
|
|
1707
|
+
};
|
|
1708
|
+
BB.Utils = (() => {
|
|
1709
|
+
const padZero = (num) => {
|
|
1710
|
+
return num.toString().padStart(2, '0');
|
|
1711
|
+
}
|
|
1712
|
+
const TimeCodesFramesTranslator = (() => {
|
|
1713
|
+
const printTimecode = (timecode, duplicated, dot2) => {
|
|
1714
|
+
let str = "";
|
|
1715
|
+
if (Array.isArray(timecode)) {
|
|
1716
|
+
str = [
|
|
1717
|
+
padZero(timecode[0]),
|
|
1718
|
+
padZero(timecode[1]),
|
|
1719
|
+
padZero(timecode[2]),
|
|
1720
|
+
padZero(timecode[3])
|
|
1721
|
+
].join(':');
|
|
1722
|
+
if (dot2==true){
|
|
1723
|
+
if(timecode[4]==1 && (duplicated === undefined || duplicated))
|
|
1724
|
+
str += ".2";
|
|
1725
|
+
else
|
|
1726
|
+
str += ".1";
|
|
1727
|
+
}
|
|
1728
|
+
else{
|
|
1729
|
+
if(timecode[4]==1 && (duplicated === undefined || duplicated))
|
|
1730
|
+
str += ".1";
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
else {
|
|
1734
|
+
const delimiter = ":";
|
|
1735
|
+
str = ("0" + timecode.h).slice(-2) + delimiter + ("0" + timecode.m).slice(-2) + delimiter + ("0" + timecode.s).slice(-2) + delimiter + ("0" + timecode.f).slice(-2);
|
|
1736
|
+
if (duplicated) str += "." + (timecode.field ? "1" : "2");
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
return str;
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
const timeCodeStr2TimeCodeArray = (timecodeStr) => {
|
|
1743
|
+
let tokens = timecodeStr.split(":");
|
|
1744
|
+
if (tokens.length != 4) {
|
|
1745
|
+
console.error("timeCodeStr2TimeCodeArray: Failed to parse " + timecodeStr);
|
|
1746
|
+
return [0,0,0,0,0];
|
|
1747
|
+
}
|
|
1748
|
+
let timeCodeArray = [
|
|
1749
|
+
parseInt(tokens[0]),//hours
|
|
1750
|
+
parseInt(tokens[1]),//minutes
|
|
1751
|
+
parseInt(tokens[2])];//seconds
|
|
1752
|
+
if (tokens[3].indexOf(".") >= 0) {
|
|
1753
|
+
let framesAndField = tokens[3].split(".");
|
|
1754
|
+
timeCodeArray.push(parseInt(framesAndField[0]));//frames
|
|
1755
|
+
timeCodeArray.push(parseInt(framesAndField[1]));//field
|
|
1756
|
+
}
|
|
1757
|
+
else {
|
|
1758
|
+
timeCodeArray.push(parseInt(tokens[3]));//frames
|
|
1759
|
+
timeCodeArray.push(0);//field
|
|
1760
|
+
}
|
|
1761
|
+
return timeCodeArray;
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
|
|
1765
|
+
const Timecode2Frames = (timecode, fps, duplicated) => { //timecode [hh,mm,ss,ff,.1] //.1= 1|0, fps (60,59.94,30.29.96), duplicated (true|false)
|
|
1766
|
+
// finding counting fps and dropped frames
|
|
1767
|
+
let countingFps=60;
|
|
1768
|
+
let dropFrames=0;
|
|
1769
|
+
if (Math.floor(fps)==60)
|
|
1770
|
+
{
|
|
1771
|
+
countingFps = 60;
|
|
1772
|
+
dropFrames=0;
|
|
1773
|
+
}
|
|
1774
|
+
else if (Math.floor(fps)==59)
|
|
1775
|
+
{
|
|
1776
|
+
countingFps = 60;
|
|
1777
|
+
dropFrames=4;
|
|
1778
|
+
}
|
|
1779
|
+
else if (Math.floor(fps)==30)
|
|
1780
|
+
{
|
|
1781
|
+
countingFps = 30;
|
|
1782
|
+
dropFrames=0;
|
|
1783
|
+
}
|
|
1784
|
+
else if (Math.floor(fps)==29)
|
|
1785
|
+
{
|
|
1786
|
+
countingFps = 30;
|
|
1787
|
+
dropFrames=2;
|
|
1788
|
+
}
|
|
1789
|
+
let result=0;
|
|
1790
|
+
let framesPerMinute = Math.floor(fps*60); //3600|3596|1800|1798
|
|
1791
|
+
let framesPer10Minutes = Math.floor(fps*60*10); //36000|35964|18000|17982
|
|
1792
|
+
let framesPerHour = framesPer10Minutes*6;
|
|
1793
|
+
let hh=timecode[0];
|
|
1794
|
+
let mm=timecode[1];
|
|
1795
|
+
let ss=timecode[2];
|
|
1796
|
+
let ff=timecode[3];
|
|
1797
|
+
let dotOne = timecode[4];//(timecode[4]==0)?false:true;
|
|
1798
|
+
result += hh*framesPerHour;
|
|
1799
|
+
result += mm*(countingFps*60);
|
|
1800
|
+
result += ss*countingFps;
|
|
1801
|
+
if (duplicated)
|
|
1802
|
+
result += ff * 2 + (dotOne-1); //we are on .1|.2 mode
|
|
1803
|
+
else
|
|
1804
|
+
result += ff;
|
|
1805
|
+
result -= dropFrames*(mm);
|
|
1806
|
+
result += dropFrames*(Math.floor(mm/10));//drop Frames on every minute exept every 10th
|
|
1807
|
+
return result;
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
const Frames2Timecode = (frames, fps, duplicated) => { //timecode [hh,mm,ss,ff,.1] //.1= 1|0, fps (60,59.94,30.29.96), duplicated (true|false)
|
|
1811
|
+
|
|
1812
|
+
// finding counting fps and dropped frames
|
|
1813
|
+
let countingFps=60;
|
|
1814
|
+
let dropFrames=0;
|
|
1815
|
+
if (Math.floor(fps)==60)
|
|
1816
|
+
{
|
|
1817
|
+
countingFps = 60;
|
|
1818
|
+
dropFrames=0;
|
|
1819
|
+
}
|
|
1820
|
+
else if (Math.floor(fps)==59)
|
|
1821
|
+
{
|
|
1822
|
+
countingFps = 60;
|
|
1823
|
+
dropFrames=4;
|
|
1824
|
+
}
|
|
1825
|
+
else if (Math.floor(fps)==30)
|
|
1826
|
+
{
|
|
1827
|
+
countingFps = 30;
|
|
1828
|
+
dropFrames=0;
|
|
1829
|
+
}
|
|
1830
|
+
else if (Math.floor(fps)==29)
|
|
1831
|
+
{
|
|
1832
|
+
countingFps = 30;
|
|
1833
|
+
dropFrames=2;
|
|
1834
|
+
}
|
|
1835
|
+
let result=0;
|
|
1836
|
+
let framesPerMinute = Math.floor(fps*60); //3600|3596|1800|1798
|
|
1837
|
+
let framesPer10Minutes = Math.floor(fps*60*10) //36000|35964|18000|17982
|
|
1838
|
+
let framesPerHour = framesPer10Minutes*6;
|
|
1839
|
+
|
|
1840
|
+
let framesLeft=frames;
|
|
1841
|
+
let hh = Math.floor(framesLeft / framesPerHour);
|
|
1842
|
+
framesLeft-=hh*framesPerHour;
|
|
1843
|
+
let mm10 = Math.floor(framesLeft / framesPer10Minutes);
|
|
1844
|
+
|
|
1845
|
+
framesLeft-=mm10*framesPer10Minutes;
|
|
1846
|
+
|
|
1847
|
+
if (framesLeft >= countingFps*60) //3600 (in 59.94fps)
|
|
1848
|
+
framesLeft+= dropFrames*(Math.floor((framesLeft-countingFps*60)/framesPerMinute)+1); //3600 /3596 (in 59.94fps)
|
|
1849
|
+
|
|
1850
|
+
//framesLeft-= Math.floor(framesLeft / framesPerMinute)*(dropFrames)
|
|
1851
|
+
|
|
1852
|
+
let mm = Math.floor(framesLeft / (countingFps*60)); //3600 (in 59.94fps)
|
|
1853
|
+
|
|
1854
|
+
framesLeft-=mm*(countingFps*60); //3600 (in 59.94fps)
|
|
1855
|
+
|
|
1856
|
+
let ss = Math.floor((framesLeft) / countingFps);
|
|
1857
|
+
framesLeft-=ss*countingFps;
|
|
1858
|
+
let ff = framesLeft;
|
|
1859
|
+
mm+=mm10*10;
|
|
1860
|
+
|
|
1861
|
+
dotOne=0;
|
|
1862
|
+
if (duplicated)
|
|
1863
|
+
{
|
|
1864
|
+
dotOne = ff % 2;
|
|
1865
|
+
ff=Math.floor(ff/2);
|
|
1866
|
+
}
|
|
1867
|
+
let returnTimecode = [hh,mm,ss,ff,dotOne];
|
|
1868
|
+
return returnTimecode;
|
|
1869
|
+
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
const TimeCodeAddFrames = (tc, framesToAdd, fps) => {
|
|
1873
|
+
let duplicated = fps > 30;
|
|
1874
|
+
let frames = Timecode2Frames(tc, fps, duplicated);
|
|
1875
|
+
frames += framesToAdd;
|
|
1876
|
+
return Frames2Timecode(frames, fps, duplicated);
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
const TimeCodeArray2TimeCodeJson = (timeCodeArray) => {
|
|
1880
|
+
return {h:timeCodeArray[0],m:timeCodeArray[1],s:timeCodeArray[2],f:timeCodeArray[3],field:timeCodeArray[4]};
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
const timeDate2TCString = (timeDate, fps) => {
|
|
1884
|
+
let tc = BB.Utils.timeDate2TC(timeDate,fps);
|
|
1885
|
+
//didn't check new version
|
|
1886
|
+
if (fps>59){
|
|
1887
|
+
return BB.Utils.TimeCodesFramesTranslator.printTimecode(tcFromMessage,true);
|
|
1888
|
+
}
|
|
1889
|
+
else{
|
|
1890
|
+
return BB.Utils.TimeCodesFramesTranslator.printTimecode(tcFromMessage,false);
|
|
1891
|
+
}
|
|
1892
|
+
//--------------
|
|
1893
|
+
//old
|
|
1894
|
+
let platform = BB.Utils.Platform;
|
|
1895
|
+
|
|
1896
|
+
let timeStampStr = "";
|
|
1897
|
+
if (platform.isApple()){
|
|
1898
|
+
timeStampStr = timeDate;
|
|
1899
|
+
}else{
|
|
1900
|
+
timeStampStr = timeDate.toISOString();
|
|
1901
|
+
}
|
|
1902
|
+
let timeStr = timeStampStr.split("T")[1];
|
|
1903
|
+
let arr1 = timeStr.split(":");
|
|
1904
|
+
let hrs = parseInt(arr1[0]);
|
|
1905
|
+
let mins = parseInt(arr1[1]);
|
|
1906
|
+
let arr2 = arr1[2].split(".");
|
|
1907
|
+
let secs = parseInt(arr2[0]);
|
|
1908
|
+
let msec = parseInt(arr2[1].substring(0, 3));
|
|
1909
|
+
let frames = Math.round(((msec / 1000) * fps) /*+ 0.45*/);
|
|
1910
|
+
if (fps>59){
|
|
1911
|
+
|
|
1912
|
+
let field = (frames % 2 == 0 ? 1 : 2);
|
|
1913
|
+
frames = Math.floor(frames / 2);
|
|
1914
|
+
let str = hrs.toString().padStart(2, '0') + ":" +
|
|
1915
|
+
mins.toString().padStart(2, '0') + ":" +
|
|
1916
|
+
secs.toString().padStart(2, '0') + ":" +
|
|
1917
|
+
frames.toString().padStart(2, '0') + "." + field.toString();
|
|
1918
|
+
return str;
|
|
1919
|
+
}
|
|
1920
|
+
else {
|
|
1921
|
+
let str = hrs.toString().padStart(2, '0') + ":" +
|
|
1922
|
+
mins.toString().padStart(2, '0') + ":" +
|
|
1923
|
+
secs.toString().padStart(2, '0') + ":" +
|
|
1924
|
+
frames.toString().padStart(2, '0');
|
|
1925
|
+
return str;
|
|
1926
|
+
}
|
|
1927
|
+
return str;
|
|
1928
|
+
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
const timeDate2TC = (timeDate, fps) => {
|
|
1932
|
+
let platform = BB.Utils.Platform;
|
|
1933
|
+
let timeStampStr = "";
|
|
1934
|
+
if (platform.isApple()){
|
|
1935
|
+
timeStampStr = timeDate;
|
|
1936
|
+
}else{
|
|
1937
|
+
timeStampStr = timeDate.toISOString();
|
|
1938
|
+
}
|
|
1939
|
+
let timeStr = timeStampStr.split("T")[1];
|
|
1940
|
+
let arr1 = timeStr.split(":");
|
|
1941
|
+
let hrs = parseInt(arr1[0]);
|
|
1942
|
+
let mins = parseInt(arr1[1]);
|
|
1943
|
+
let arr2 = arr1[2].split(".");
|
|
1944
|
+
let secs = parseInt(arr2[0]);
|
|
1945
|
+
let msec = parseInt(arr2[1].substring(0, 3));
|
|
1946
|
+
//let frames = Math.round(((msec / 1000) * fps)+0.02 /*+ 0.45*/);
|
|
1947
|
+
if (fps>59){
|
|
1948
|
+
//let field = (frames % 2 == 0 ? 1 : 2);
|
|
1949
|
+
//frames = Math.floor(frames / 2);
|
|
1950
|
+
//let returnTimecode = [hrs,mins,secs,frames,field-1];
|
|
1951
|
+
let allFrames = Math.round((msec/1000+secs+mins*60+hrs*60*60)*fps);
|
|
1952
|
+
let returnTimecode = BB.Utils.TimeCodesFramesTranslator.Frames2Timecode(allFrames,fps,true);
|
|
1953
|
+
return returnTimecode;
|
|
1954
|
+
}
|
|
1955
|
+
else
|
|
1956
|
+
{
|
|
1957
|
+
//let returnTimecode = [hrs,mins,secs,frames,0];
|
|
1958
|
+
//let returnTimecode = BB.Utils.TimeCodesFramesTranslator.Frames2Timecode(frames+secs*fps+mins*fps*60+hrs*fps*60*60,fps,false);
|
|
1959
|
+
let allFrames = Math.round((msec/1000+secs+mins*60+hrs*60*60)*fps);
|
|
1960
|
+
let returnTimecode = BB.Utils.TimeCodesFramesTranslator.Frames2Timecode(allFrames,fps,false);
|
|
1961
|
+
return returnTimecode;
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
|
|
1967
|
+
return {
|
|
1968
|
+
printTimecode:printTimecode,
|
|
1969
|
+
timeCodeStr2TimeCodeArray:timeCodeStr2TimeCodeArray,
|
|
1970
|
+
Timecode2Frames:Timecode2Frames,
|
|
1971
|
+
Frames2Timecode:Frames2Timecode,
|
|
1972
|
+
TimeCodeAddFrames:TimeCodeAddFrames,
|
|
1973
|
+
TimeCodeArray2TimeCodeJson:TimeCodeArray2TimeCodeJson,
|
|
1974
|
+
timeDate2TCString:timeDate2TCString,
|
|
1975
|
+
timeDate2TC:timeDate2TC
|
|
1976
|
+
};
|
|
1977
|
+
|
|
1978
|
+
})();
|
|
1979
|
+
|
|
1980
|
+
const MedianCalculator = (maxSize_) => {
|
|
1981
|
+
let maxSize = maxSize_ || 9999999999;
|
|
1982
|
+
let values = [];
|
|
1983
|
+
let count = 0;
|
|
1984
|
+
|
|
1985
|
+
const add = (value) => {
|
|
1986
|
+
++count;
|
|
1987
|
+
values.push(value);
|
|
1988
|
+
while (values.length > maxSize) values.shift();
|
|
1989
|
+
};
|
|
1990
|
+
const getMedian = () => {
|
|
1991
|
+
if (values.length == 0) return -1;
|
|
1992
|
+
let sortedValues = Array.from(values).sort((a,b) => { return a - b; });
|
|
1993
|
+
return sortedValues[parseInt(sortedValues.length/2)];
|
|
1994
|
+
};
|
|
1995
|
+
const howMany = () => {
|
|
1996
|
+
return count;
|
|
1997
|
+
};
|
|
1998
|
+
|
|
1999
|
+
return {
|
|
2000
|
+
add,
|
|
2001
|
+
getMedian,
|
|
2002
|
+
howMany
|
|
2003
|
+
};
|
|
2004
|
+
};
|
|
2005
|
+
|
|
2006
|
+
//todo: make it use one function. Now they are similar
|
|
2007
|
+
const Platform = (() => {
|
|
2008
|
+
const getCurrentPlatform = () => {
|
|
2009
|
+
const userAgent = navigator.userAgent;
|
|
2010
|
+
if (/windows/i.test(userAgent)) {
|
|
2011
|
+
return 'Windows';
|
|
2012
|
+
} else if (/android/i.test(userAgent)) {
|
|
2013
|
+
return 'Android';
|
|
2014
|
+
} else if (/iphone|ipad|ipod/i.test(userAgent)) {
|
|
2015
|
+
return 'iOS';
|
|
2016
|
+
} else if (/mac/i.test(userAgent)) {
|
|
2017
|
+
let isIPadAnyhow = 'ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0;
|
|
2018
|
+
if (isIPadAnyhow) return 'iPad';
|
|
2019
|
+
else return 'Mac';
|
|
2020
|
+
} else if (/ipad/i.test(userAgent)) {
|
|
2021
|
+
return 'iPad';
|
|
2022
|
+
} else {
|
|
2023
|
+
return 'Something else';
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
const isMac = () => {
|
|
2027
|
+
let platform = getCurrentPlatform();
|
|
2028
|
+
return platform =='Mac';
|
|
2029
|
+
};
|
|
2030
|
+
const isiPad = () => {
|
|
2031
|
+
let platform = getCurrentPlatform();
|
|
2032
|
+
return platform =='iPad';
|
|
2033
|
+
};
|
|
2034
|
+
const isiPhone = () => {
|
|
2035
|
+
let platform = getCurrentPlatform();
|
|
2036
|
+
return platform =='iOS';
|
|
2037
|
+
};
|
|
2038
|
+
const isApple = () => {
|
|
2039
|
+
let platform = getCurrentPlatform();
|
|
2040
|
+
return platform == 'iOS' || platform == 'Mac' || platform == 'iPad';
|
|
2041
|
+
}
|
|
2042
|
+
const isWindows = () => {
|
|
2043
|
+
//return true;
|
|
2044
|
+
let platform = getCurrentPlatform();
|
|
2045
|
+
return platform == 'Windows';
|
|
2046
|
+
}
|
|
2047
|
+
const isAndroid = () => {
|
|
2048
|
+
let platform = getCurrentPlatform();
|
|
2049
|
+
return platform == 'Android';
|
|
2050
|
+
}
|
|
2051
|
+
const getDeviceDescription = () => {
|
|
2052
|
+
let deviceType = getCurrentPlatform();
|
|
2053
|
+
if (deviceType == 'Windows') return "a Windows PC";
|
|
2054
|
+
else if (deviceType == 'Android') return "an Android phone";
|
|
2055
|
+
else if (deviceType == 'iOS') return "an iPhone";
|
|
2056
|
+
else if (deviceType == 'Mac') return "a Mac";
|
|
2057
|
+
else if (deviceType == 'iPad') return "an iPad";
|
|
2058
|
+
else return "an Unknown device";
|
|
2059
|
+
};
|
|
2060
|
+
const getDeviceName = () => {
|
|
2061
|
+
let module = {
|
|
2062
|
+
options: [],
|
|
2063
|
+
header: [navigator.platform, navigator.userAgent, navigator.appVersion, navigator.vendor, window.opera],
|
|
2064
|
+
dataos: [
|
|
2065
|
+
{ name: 'Windows Phone', value: 'Windows Phone', version: 'OS' },
|
|
2066
|
+
{ name: 'Windows', value: 'Win', version: 'NT' },
|
|
2067
|
+
{ name: 'iPhone', value: 'iPhone', version: 'OS' },
|
|
2068
|
+
{ name: 'iPad', value: 'iPad', version: 'OS' },
|
|
2069
|
+
{ name: 'Kindle', value: 'Silk', version: 'Silk' },
|
|
2070
|
+
{ name: 'Android', value: 'Android', version: 'Android' },
|
|
2071
|
+
{ name: 'PlayBook', value: 'PlayBook', version: 'OS' },
|
|
2072
|
+
{ name: 'BlackBerry', value: 'BlackBerry', version: '/' },
|
|
2073
|
+
{ name: 'Macintosh', value: 'Mac', version: 'OS X' },
|
|
2074
|
+
{ name: 'Linux', value: 'Linux', version: 'rv' },
|
|
2075
|
+
{ name: 'Palm', value: 'Palm', version: 'PalmOS' }
|
|
2076
|
+
],
|
|
2077
|
+
databrowser: [
|
|
2078
|
+
{ name: 'Chrome', value: 'Chrome', version: 'Chrome' },
|
|
2079
|
+
{ name: 'Firefox', value: 'Firefox', version: 'Firefox' },
|
|
2080
|
+
{ name: 'Safari', value: 'Safari', version: 'Version' },
|
|
2081
|
+
{ name: 'Internet Explorer', value: 'MSIE', version: 'MSIE' },
|
|
2082
|
+
{ name: 'Opera', value: 'Opera', version: 'Opera' },
|
|
2083
|
+
{ name: 'BlackBerry', value: 'CLDC', version: 'CLDC' },
|
|
2084
|
+
{ name: 'Mozilla', value: 'Mozilla', version: 'Mozilla' }
|
|
2085
|
+
],
|
|
2086
|
+
init: function () {
|
|
2087
|
+
let agent = this.header.join(' '),
|
|
2088
|
+
os = this.matchItem(agent, this.dataos),
|
|
2089
|
+
browser = this.matchItem(agent, this.databrowser);
|
|
2090
|
+
|
|
2091
|
+
return { os: os, browser: browser };
|
|
2092
|
+
},
|
|
2093
|
+
matchItem: function (string, data) {
|
|
2094
|
+
let i = 0,
|
|
2095
|
+
j = 0,
|
|
2096
|
+
html = '',
|
|
2097
|
+
regex,
|
|
2098
|
+
regexv,
|
|
2099
|
+
match,
|
|
2100
|
+
matches,
|
|
2101
|
+
version;
|
|
2102
|
+
|
|
2103
|
+
for (i = 0; i < data.length; i += 1) {
|
|
2104
|
+
regex = new RegExp(data[i].value, 'i');
|
|
2105
|
+
match = regex.test(string);
|
|
2106
|
+
if (match) {
|
|
2107
|
+
regexv = new RegExp(data[i].version + '[- /:;]([\\d._]+)', 'i');
|
|
2108
|
+
matches = string.match(regexv);
|
|
2109
|
+
version = '';
|
|
2110
|
+
if (matches) { if (matches[1]) { matches = matches[1]; } }
|
|
2111
|
+
if (matches) {
|
|
2112
|
+
matches = matches.split(/[._]+/);
|
|
2113
|
+
for (j = 0; j < matches.length; j += 1) {
|
|
2114
|
+
if (j === 0) {
|
|
2115
|
+
version += matches[j] + '.';
|
|
2116
|
+
} else {
|
|
2117
|
+
version += matches[j];
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
} else {
|
|
2121
|
+
version = '0';
|
|
2122
|
+
}
|
|
2123
|
+
return {
|
|
2124
|
+
name: data[i].name,
|
|
2125
|
+
version: parseFloat(version)
|
|
2126
|
+
};
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
return { name: 'unknown', version: 0 };
|
|
2130
|
+
}
|
|
2131
|
+
};
|
|
2132
|
+
|
|
2133
|
+
let e = module.init();
|
|
2134
|
+
return e.os.name;
|
|
2135
|
+
};
|
|
2136
|
+
const getBrowserName = () => {
|
|
2137
|
+
const userAgent = navigator.userAgent;
|
|
2138
|
+
|
|
2139
|
+
if (userAgent.indexOf("Chrome") > -1 && userAgent.indexOf("Edge") === -1) {
|
|
2140
|
+
return "Chrome";
|
|
2141
|
+
} else if (userAgent.indexOf("Safari") > -1 && userAgent.indexOf("Chrome") === -1) {
|
|
2142
|
+
return "Safari";
|
|
2143
|
+
} else if (userAgent.indexOf("Firefox") > -1) {
|
|
2144
|
+
return "Firefox";
|
|
2145
|
+
} else if (userAgent.indexOf("MSIE") > -1 || userAgent.indexOf("Trident") > -1) {
|
|
2146
|
+
return "Internet Explorer";
|
|
2147
|
+
} else if (userAgent.indexOf("Edge") > -1) {
|
|
2148
|
+
return "Edge";
|
|
2149
|
+
} else {
|
|
2150
|
+
return "Unknown";
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
const isSafariMac = () => {
|
|
2154
|
+
return (isMac()&&(getBrowserName() == "Safari"))
|
|
2155
|
+
}
|
|
2156
|
+
const isChromeMac = () => {
|
|
2157
|
+
return (isMac()&&(getBrowserName() == "Chrome"))
|
|
2158
|
+
}
|
|
2159
|
+
const isChromeWin = () => {
|
|
2160
|
+
return (isWindows()&&(getBrowserName() == "Chrome"))
|
|
2161
|
+
}
|
|
2162
|
+
return {
|
|
2163
|
+
getDeviceName,
|
|
2164
|
+
getCurrentPlatform,
|
|
2165
|
+
isApple,
|
|
2166
|
+
isWindows,
|
|
2167
|
+
isAndroid,
|
|
2168
|
+
isMac,
|
|
2169
|
+
isiPhone,
|
|
2170
|
+
isiPad,
|
|
2171
|
+
getDeviceDescription,
|
|
2172
|
+
getBrowserName,
|
|
2173
|
+
isSafariMac,
|
|
2174
|
+
isChromeMac,
|
|
2175
|
+
isChromeWin
|
|
2176
|
+
}
|
|
2177
|
+
})();
|
|
2178
|
+
|
|
2179
|
+
const getMediaTimeFPS = (frameRateMode) => {
|
|
2180
|
+
let fps = 60;
|
|
2181
|
+
switch(frameRateMode) {
|
|
2182
|
+
case BB.FrameRateMode.Src60_MediaTime60:
|
|
2183
|
+
fps = 60;
|
|
2184
|
+
break;
|
|
2185
|
+
case BB.FrameRateMode.Src59_MediaTime60:
|
|
2186
|
+
fps = 60;
|
|
2187
|
+
break;
|
|
2188
|
+
case BB.FrameRateMode.Src59_MediaTime59:
|
|
2189
|
+
fps = 60000/1001;
|
|
2190
|
+
break;
|
|
2191
|
+
case BB.FrameRateMode.Src29_MediaTime29:
|
|
2192
|
+
fps = 30000/1001;
|
|
2193
|
+
break;
|
|
2194
|
+
default:
|
|
2195
|
+
console.error("BB.Utils.getMediaTimeFPS: Unknown FrameRate mode");
|
|
2196
|
+
break;
|
|
2197
|
+
};
|
|
2198
|
+
return fps;
|
|
2199
|
+
};
|
|
2200
|
+
const getSourceFPS = (frameRateMode) => {
|
|
2201
|
+
let fps = 60;
|
|
2202
|
+
switch(frameRateMode) {
|
|
2203
|
+
case BB.FrameRateMode.Src60_MediaTime60:
|
|
2204
|
+
fps = 60;
|
|
2205
|
+
break;
|
|
2206
|
+
case BB.FrameRateMode.Src59_MediaTime60:
|
|
2207
|
+
fps = 60000/1001;
|
|
2208
|
+
break;
|
|
2209
|
+
case BB.FrameRateMode.Src59_MediaTime59:
|
|
2210
|
+
fps = 60000/1001;
|
|
2211
|
+
break;
|
|
2212
|
+
case BB.FrameRateMode.Src29_MediaTime29:
|
|
2213
|
+
fps = 30000/1001;
|
|
2214
|
+
break;
|
|
2215
|
+
default:
|
|
2216
|
+
console.error("BB.Utils.getSourceFPS: Unknown FrameRate mode");
|
|
2217
|
+
break;
|
|
2218
|
+
};
|
|
2219
|
+
return fps;
|
|
2220
|
+
};
|
|
2221
|
+
const setMediaFPS = (newFPS, options) => {
|
|
2222
|
+
const equil = (one, two) => {
|
|
2223
|
+
if (Math.abs(one-two)<0.01)
|
|
2224
|
+
return true;
|
|
2225
|
+
else
|
|
2226
|
+
return false;
|
|
2227
|
+
}
|
|
2228
|
+
|
|
2229
|
+
if (equil(newFPS,60)){
|
|
2230
|
+
if (equil(BB.Utils.getSourceFPS(options.frameRateMode),60))
|
|
2231
|
+
options.frameRateMode = BB.FrameRateMode.Src60_MediaTime60;
|
|
2232
|
+
else if(equil(BB.Utils.getSourceFPS(options.frameRateMode),60000/1001))
|
|
2233
|
+
options.frameRateMode = BB.FrameRateMode.Src59_MediaTime60;
|
|
2234
|
+
else
|
|
2235
|
+
console.error("setMediaFPS: Unknown Source FrameRate mode");
|
|
2236
|
+
}
|
|
2237
|
+
else if (equil(newFPS,60000/1001)){
|
|
2238
|
+
if (equil(BB.Utils.getSourceFPS(options.frameRateMode),60))
|
|
2239
|
+
options.frameRateMode = BB.FrameRateMode.Src60_MediaTime59;
|
|
2240
|
+
else if(equil(BB.Utils.getSourceFPS(options.frameRateMode),60000/1001))
|
|
2241
|
+
options.frameRateMode = BB.FrameRateMode.Src59_MediaTime59;
|
|
2242
|
+
else
|
|
2243
|
+
console.error("setMediaFPS: Unknown Source FrameRate mode");
|
|
2244
|
+
}
|
|
2245
|
+
else if (equil(newFPS,30000/1001)){
|
|
2246
|
+
if (equil(BB.Utils.getSourceFPS(options.frameRateMode),30000/1001))
|
|
2247
|
+
options.frameRateMode = BB.FrameRateMode.Src29_MediaTime29;
|
|
2248
|
+
else
|
|
2249
|
+
console.error("setMediaFPS: Unknown Source FrameRate mode, fps = " + newFPS);
|
|
2250
|
+
}
|
|
2251
|
+
else
|
|
2252
|
+
console.error("setMediaFPS: Unknown Media FrameRate mode");
|
|
2253
|
+
};
|
|
2254
|
+
|
|
2255
|
+
|
|
2256
|
+
const FPSGetter = () => {
|
|
2257
|
+
|
|
2258
|
+
const possibleFps29_30 = [
|
|
2259
|
+
30000/1001,//29.97
|
|
2260
|
+
30/1//30,0
|
|
2261
|
+
|
|
2262
|
+
];
|
|
2263
|
+
const possibleFps59_60 = [
|
|
2264
|
+
60000/1001,//59.94
|
|
2265
|
+
60/1///60
|
|
2266
|
+
];
|
|
2267
|
+
let bestFPSMatches = [];
|
|
2268
|
+
let deltaStorage = [];
|
|
2269
|
+
let deltaCount =0;
|
|
2270
|
+
let lastMediaTimeForFPSGetting = -1;
|
|
2271
|
+
//let fps = 60;
|
|
2272
|
+
let count = 0;
|
|
2273
|
+
const addTime = (mediaTime) => {
|
|
2274
|
+
count++;
|
|
2275
|
+
//let fraction2PossibleFPS = new Map();
|
|
2276
|
+
let minFraction=999;
|
|
2277
|
+
let minCandidateFps=0;
|
|
2278
|
+
if (lastMediaTimeForFPSGetting == -1){
|
|
2279
|
+
lastMediaTimeForFPSGetting = mediaTime;
|
|
2280
|
+
return;
|
|
2281
|
+
}
|
|
2282
|
+
else
|
|
2283
|
+
{
|
|
2284
|
+
let delta = mediaTime - lastMediaTimeForFPSGetting;
|
|
2285
|
+
if (deltaCount<100){
|
|
2286
|
+
deltaStorage.push(delta);
|
|
2287
|
+
deltaCount++;
|
|
2288
|
+
return;
|
|
2289
|
+
}
|
|
2290
|
+
else{
|
|
2291
|
+
deltaStorage.push(delta);
|
|
2292
|
+
deltaStorage.shift();
|
|
2293
|
+
}
|
|
2294
|
+
let total = 0;
|
|
2295
|
+
for(let i = 0; i < deltaStorage.length; i++) {
|
|
2296
|
+
total += deltaStorage[i];
|
|
2297
|
+
}
|
|
2298
|
+
delta = total / deltaStorage.length;
|
|
2299
|
+
//console.log("delta= " + delta.toFixed(5) + " fps= " + (1/delta).toFixed(2));
|
|
2300
|
+
if ((1/delta) < 40)
|
|
2301
|
+
possibleFps = possibleFps29_30;
|
|
2302
|
+
else
|
|
2303
|
+
possibleFps = possibleFps59_60;
|
|
2304
|
+
}
|
|
2305
|
+
//possibleFps = possibleFps59_60;
|
|
2306
|
+
lastMediaTimeForFPSGetting = mediaTime;
|
|
2307
|
+
|
|
2308
|
+
for (let i = 0; i < possibleFps.length; ++i) {
|
|
2309
|
+
let candidateFps = possibleFps[i];
|
|
2310
|
+
let dblFrame = mediaTime*candidateFps;
|
|
2311
|
+
let intFrame = Math.round(dblFrame);
|
|
2312
|
+
let fraction = Math.abs(dblFrame - intFrame);
|
|
2313
|
+
if (fraction < minFraction)
|
|
2314
|
+
{
|
|
2315
|
+
minFraction = fraction;
|
|
2316
|
+
minCandidateFps = candidateFps;
|
|
2317
|
+
}
|
|
2318
|
+
|
|
2319
|
+
//fraction2PossibleFPS.set(fraction, candidateFps);
|
|
2320
|
+
}
|
|
2321
|
+
let bestFPSMatch = minCandidateFps;//fraction2PossibleFPS.entries().next().value[1];
|
|
2322
|
+
bestFPSMatches.push(bestFPSMatch);
|
|
2323
|
+
//console.log("bestFPSMatch = " + bestFPSMatch);
|
|
2324
|
+
if(count>1000)
|
|
2325
|
+
bestFPSMatches.shift(); //deleting the first el
|
|
2326
|
+
}
|
|
2327
|
+
const getProbableFPS = () => {
|
|
2328
|
+
let count = {};
|
|
2329
|
+
let mostOccurringValue;
|
|
2330
|
+
let maxCount = 0;
|
|
2331
|
+
|
|
2332
|
+
// Count the occurrences of each value in the array
|
|
2333
|
+
for (let i = 0; i < bestFPSMatches.length; i++) {
|
|
2334
|
+
let value = bestFPSMatches[i];
|
|
2335
|
+
count[value] = (count[value] || 0) + 1;
|
|
2336
|
+
|
|
2337
|
+
// Update the most occurring value and its count if necessary
|
|
2338
|
+
if (count[value] > maxCount) {
|
|
2339
|
+
mostOccurringValue = value;
|
|
2340
|
+
maxCount = count[value];
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
|
|
2344
|
+
return mostOccurringValue;
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2347
|
+
const howMany = () => {
|
|
2348
|
+
return count;
|
|
2349
|
+
};
|
|
2350
|
+
|
|
2351
|
+
return {
|
|
2352
|
+
addTime:addTime,
|
|
2353
|
+
getProbableFPS:getProbableFPS,
|
|
2354
|
+
howMany:howMany
|
|
2355
|
+
};
|
|
2356
|
+
|
|
2357
|
+
|
|
2358
|
+
};
|
|
2359
|
+
|
|
2360
|
+
return {
|
|
2361
|
+
TimeCodesFramesTranslator,
|
|
2362
|
+
MedianCalculator,
|
|
2363
|
+
Platform,
|
|
2364
|
+
getMediaTimeFPS,
|
|
2365
|
+
getSourceFPS,
|
|
2366
|
+
setMediaFPS,
|
|
2367
|
+
FPSGetter
|
|
2368
|
+
}
|
|
2369
|
+
})();
|
|
2370
|
+
BB.Socket = class {
|
|
2371
|
+
constructor(callback) {
|
|
2372
|
+
this.isConnected = false;
|
|
2373
|
+
this.folderName = "";
|
|
2374
|
+
this.callback = callback;
|
|
2375
|
+
this.name = "";
|
|
2376
|
+
}
|
|
2377
|
+
|
|
2378
|
+
Connect (channel){
|
|
2379
|
+
if (typeof io == "undefined" || this.isConnected) return;
|
|
2380
|
+
this.isConnected = true;
|
|
2381
|
+
const socket = io("https://www.virtualott.com");
|
|
2382
|
+
socket.on("connect", () => {
|
|
2383
|
+
console.log("socket connected");
|
|
2384
|
+
let platform = BB.Utils.Platform;
|
|
2385
|
+
socket.emit('setinfo', {device:platform.getDeviceName()},(info)=>{
|
|
2386
|
+
this.name = info.name;
|
|
2387
|
+
socket.emit("join", channel, (data) => {
|
|
2388
|
+
console.log(`join ${channel} room`);
|
|
2389
|
+
this.callback("join", data);
|
|
2390
|
+
});
|
|
2391
|
+
});
|
|
2392
|
+
});
|
|
2393
|
+
socket.on("error", (err) => {
|
|
2394
|
+
console.error(`error::${err}`);
|
|
2395
|
+
});
|
|
2396
|
+
socket.on('disconnect', (reason) => {
|
|
2397
|
+
console.error(`disconnect from signaling::${reason}`);
|
|
2398
|
+
});
|
|
2399
|
+
socket.on("messageroom", (message) => {
|
|
2400
|
+
this.callback("messageroom", message);
|
|
2401
|
+
console.log("messageroom: ", message);
|
|
2402
|
+
});
|
|
2403
|
+
socket.on("ads", (message) => {
|
|
2404
|
+
this.callback("ads", message);
|
|
2405
|
+
console.log("ads:", message);
|
|
2406
|
+
});
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
BB.Augmenter = (line, video, options) => {
|
|
2410
|
+
console.log("BB.Augmenter Start");
|
|
2411
|
+
// MK OnMetaData Sync Begin
|
|
2412
|
+
function processTimedMetadata(messageData, presentationTime) {
|
|
2413
|
+
// Timed metadata
|
|
2414
|
+
let finalTimeCodeData = "";
|
|
2415
|
+
if (messageData && messageData.includes("www.mediakind.com/NTC")) {
|
|
2416
|
+
const metadata = messageData.replace(/.*NTC./, "");
|
|
2417
|
+
let pos = 0;
|
|
2418
|
+
while (pos + 8 <= metadata.length) {
|
|
2419
|
+
const type = metadata.substring(pos, pos + 4);
|
|
2420
|
+
const length = ((metadata.charCodeAt(pos + 4) * 256 + metadata.charCodeAt(pos + 5)) * 256 + metadata.charCodeAt(pos + 6)) * 256 + metadata.charCodeAt(pos + 7);
|
|
2421
|
+
if (pos + 8 + length > metadata.length) {
|
|
2422
|
+
break; // oops!
|
|
2423
|
+
}
|
|
2424
|
+
const value = metadata.substring(pos + 8, pos + 8 + length);
|
|
2425
|
+
if (type == "utc_" ) {
|
|
2426
|
+
//previousTimeCode = currentTimeCode;
|
|
2427
|
+
timecodeDateTime = new Date(parseInt(value)).getTime()/1000;
|
|
2428
|
+
timecodeDelta = timecodeDateTime - presentationTime;
|
|
2429
|
+
//finalTimeCodeData += `${new Date(parseInt(value)).toISOString()} | timestamp: ${new Date(parseInt(value)).getTime()/1000} | delta: ${timecodeDelta}`;
|
|
2430
|
+
|
|
2431
|
+
}
|
|
2432
|
+
if (type == "tc__" ) {
|
|
2433
|
+
//previousTimeCode = currentTimeCode;
|
|
2434
|
+
//tcDateTime = new Date(parseInt(value)).getTime()/1000;
|
|
2435
|
+
//tcDelta = tcDateTime - presentationTime;
|
|
2436
|
+
tcTimeStamp = new Date(parseInt(value)).getTime();
|
|
2437
|
+
//finalTimeCodeData += `${new Date(parseInt(value)).toISOString()} | tcTimestamp: ${tcTimeStamp} | segmentPlaybackDateTime: ${hlsDateTime}`;
|
|
2438
|
+
|
|
2439
|
+
}
|
|
2440
|
+
if (type === "offs") {
|
|
2441
|
+
timecode_offset = (parseInt(value)/90);
|
|
2442
|
+
if (options.debug) console.log("We are HERE:TimeCode", timecode_offset);
|
|
2443
|
+
|
|
2444
|
+
|
|
2445
|
+
}
|
|
2446
|
+
pos += 8 + length;
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
2449
|
+
return finalTimeCodeData;
|
|
2450
|
+
}
|
|
2451
|
+
|
|
2452
|
+
let firstFrameTimecode = [0,0,0,0,0];
|
|
2453
|
+
function onNewMetadata(data) {
|
|
2454
|
+
// look for timed metadata and display in UI if available
|
|
2455
|
+
if ((data && data.metadata && data.metadata.id == 0) || (platform.isSafariMac())){ //mac check is temp. Need to get id from safari CallBack
|
|
2456
|
+
if (data.metadata.messageData) {
|
|
2457
|
+
// Typically seen on Chrome, Edge browsers
|
|
2458
|
+
data.metadata.messageData = processTimedMetadata(data.metadata.messageData, data.metadata.presentationTime);
|
|
2459
|
+
} else if (data.metadata.frames && data.metadata.frames.length && data.metadata.frames[0].key && data.metadata.frames[0].key.toLowerCase() === "priv") {
|
|
2460
|
+
// Typically seen on Safari browser
|
|
2461
|
+
const NTC_DATA = data.metadata.frames.filter((el) => {
|
|
2462
|
+
return ( el.info && el.info.toLowerCase().includes("ntc")) ;
|
|
2463
|
+
});
|
|
2464
|
+
if (NTC_DATA.length) {
|
|
2465
|
+
messageData = `${NTC_DATA[NTC_DATA.length - 1].info}\u0000${String.fromCharCode(...NTC_DATA[NTC_DATA.length - 1].data)}`;
|
|
2466
|
+
data.metadata.messageData = processTimedMetadata(messageData, data.metadata.presentationTime);
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
if((data.metadata.id == 0) || ((platform.isSafariMac()&&(timecode_offset == 0)))){ //mac check is temp. Need to get id from safari CallBack
|
|
2470
|
+
presentationTime = data.metadata.presentationTime * 1000;
|
|
2471
|
+
|
|
2472
|
+
offsetTimeCode = new Date(tcTimeStamp - timecode_offset);
|
|
2473
|
+
//offsetTimeCode = new Date(1721866388183);
|
|
2474
|
+
offsetTimeCodeframeNumber = Math.round((offsetTimeCode.getUTCMilliseconds() * 30 * 2)/1001)/2;
|
|
2475
|
+
decimalPartOff = offsetTimeCodeframeNumber % 1;
|
|
2476
|
+
partoff=1;
|
|
2477
|
+
if(decimalPartOff == 0.5){
|
|
2478
|
+
offsetTimeCodeframeNumber = Math.floor(offsetTimeCodeframeNumber);
|
|
2479
|
+
partoff=2;
|
|
2480
|
+
}
|
|
2481
|
+
|
|
2482
|
+
firstFrameTimecode = [offsetTimeCode.getUTCHours(),offsetTimeCode.getUTCMinutes(),offsetTimeCode.getUTCSeconds(),parseInt(offsetTimeCodeframeNumber),partoff==1?0:1];
|
|
2483
|
+
|
|
2484
|
+
//timeCodeOff = `${offsetTimeCode.getUTCHours()}:${offsetTimeCode.getUTCMinutes()}:${offsetTimeCode.getUTCSeconds()}:${parseInt(offsetTimeCodeframeNumber)}.${partoff}`;
|
|
2485
|
+
|
|
2486
|
+
//console.log("timeCodeOff=", timeCodeOff);
|
|
2487
|
+
//data.metadata.messageData = ``;
|
|
2488
|
+
//data.metadata.messageData = `TimeCode: ${timeCodeOff} | offsetTimeCodeframeNumber: ${offsetTimeCodeframeNumber} | offsetTimeCodeMilliseconds: ${offsetTimeCode.getUTCMilliseconds()}`;
|
|
2489
|
+
//dataForCSV += `${timeCodeOff},`
|
|
2490
|
+
//timedMetadata.textContent = JSON.stringify(data.metadata, null, 4);
|
|
2491
|
+
//timedMetadata.textContent+=`{"counter" : ${counter++}}`;
|
|
2492
|
+
}
|
|
2493
|
+
}
|
|
2494
|
+
|
|
2495
|
+
|
|
2496
|
+
}
|
|
2497
|
+
function onNewSegmentForMetadata(eventObj) {
|
|
2498
|
+
if (typeof globalConfig !== 'undefined')
|
|
2499
|
+
{
|
|
2500
|
+
if (typeof globalConfig.MKsync !== 'undefined')
|
|
2501
|
+
if (globalConfig.MKsync=="no")
|
|
2502
|
+
{
|
|
2503
|
+
return; //Temp for testing without MK
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2506
|
+
if (BB.Utils.getSourceFPS(options.frameRateMode) == 30000/1001)
|
|
2507
|
+
{
|
|
2508
|
+
let syncPoint = {frame:0,playerTime:0}
|
|
2509
|
+
syncPoint.frame = BB.Utils.TimeCodesFramesTranslator.Timecode2Frames(firstFrameTimecode,30000/1001,false);//@alal
|
|
2510
|
+
syncPoint.stringTime = BB.Utils.TimeCodesFramesTranslator.printTimecode(firstFrameTimecode,false,false);// timeDate2TCString(eventObj.dateTime, 30000/1001);
|
|
2511
|
+
console.log("New segment (" + eventObj.url +"): " + syncPoint.stringTime + " (" + syncPoint.frame + ")" + " playing at " + eventObj.playbackTime);
|
|
2512
|
+
syncPoint.playerTime = eventObj.playbackTime;
|
|
2513
|
+
options.syncPoint = syncPoint;
|
|
2514
|
+
}
|
|
2515
|
+
else if (BB.Utils.getSourceFPS(options.frameRateMode) == 60000/1001)
|
|
2516
|
+
{
|
|
2517
|
+
let syncPoint = {frame:0,playerTime:0}
|
|
2518
|
+
syncPoint.frame = BB.Utils.TimeCodesFramesTranslator.Timecode2Frames(firstFrameTimecode,60000/1001,true);//@alal
|
|
2519
|
+
syncPoint.stringTime = BB.Utils.TimeCodesFramesTranslator.printTimecode(firstFrameTimecode,true,true);// timeDate2TCString(eventObj.dateTime, 30000/1001);
|
|
2520
|
+
console.log("New segment (" + eventObj.url +"): " + syncPoint.stringTime + " (" + syncPoint.frame + ")" + " playing at " + eventObj.playbackTime);
|
|
2521
|
+
syncPoint.playerTime = eventObj.playbackTime;
|
|
2522
|
+
options.syncPoint = syncPoint;
|
|
2523
|
+
}
|
|
2524
|
+
if(eventObj.mimeType.includes("video")){
|
|
2525
|
+
|
|
2526
|
+
console.log("url: " + eventObj.url);
|
|
2527
|
+
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
// MK OnMetaData Sync End
|
|
2531
|
+
function onNewSegment(eventObj) {
|
|
2532
|
+
switch (eventObj.eventType) { //just for check
|
|
2533
|
+
case wmcEvents.AMC_EVENT_PLAYER_SEGMENT_PLAYBACK:
|
|
2534
|
+
let syncPoint = {frame:0,playerTime:0}
|
|
2535
|
+
|
|
2536
|
+
//if ((fps == 30000/1001) || (fps == 30))
|
|
2537
|
+
if (BB.Utils.getSourceFPS(options.frameRateMode) == 30000/1001)
|
|
2538
|
+
{
|
|
2539
|
+
console.log("New segment (" + eventObj.url +"): " + BB.Utils.TimeCodesFramesTranslator.timeDate2TCString(eventObj.dateTime, 30000/1001) + " playing at " + eventObj.playbackTime);
|
|
2540
|
+
syncPoint.frame = BB.Utils.TimeCodesFramesTranslator.Timecode2Frames(BB.Utils.TimeCodesFramesTranslator.timeDate2TC(eventObj.dateTime, 30000/1001),30000/1001,false);//@alal
|
|
2541
|
+
syncPoint.stringTime = BB.Utils.TimeCodesFramesTranslator.timeDate2TCString(eventObj.dateTime, 30000/1001);
|
|
2542
|
+
}
|
|
2543
|
+
//else ((fps == 60000/1001) || (fps == 30))
|
|
2544
|
+
if (BB.Utils.getSourceFPS(options.frameRateMode) == 60000/1001)
|
|
2545
|
+
{
|
|
2546
|
+
console.log("New segment (" + eventObj.url +"): " + BB.Utils.TimeCodesFramesTranslator.timeDate2TCString(eventObj.dateTime, 60000/1001) + " playing at " + eventObj.playbackTime);
|
|
2547
|
+
syncPoint.frame = BB.Utils.TimeCodesFramesTranslator.Timecode2Frames(BB.Utils.TimeCodesFramesTranslator.timeDate2TC(eventObj.dateTime),fps,true);//@alal
|
|
2548
|
+
syncPoint.stringTime = BB.Utils.TimeCodesFramesTranslator.timeDate2TCString(eventObj.dateTime, 60000/1001);
|
|
2549
|
+
}
|
|
2550
|
+
syncPoint.playerTime = eventObj.playbackTime;
|
|
2551
|
+
options.syncPoint = syncPoint;
|
|
2552
|
+
//bbPlayer.SetSyncPoint(syncPoint);
|
|
2553
|
+
break;
|
|
2554
|
+
default:
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
//do not need this anymore
|
|
2558
|
+
// if (player!={}){
|
|
2559
|
+
// if (options.SynchMethod == BB.SynchMethod.OnNewSegment)
|
|
2560
|
+
// {
|
|
2561
|
+
// player.addEventListener(wmcEvents.AMC_EVENT_PLAYER_SEGMENT_PLAYBACK, onNewSegment);
|
|
2562
|
+
// }
|
|
2563
|
+
// else if (options.SynchMethod == BB.SynchMethod.OnMetadata)
|
|
2564
|
+
// {
|
|
2565
|
+
// player.addEventListener(wmcEvents.AMC_EVENT_PLAYER_METADATA, onNewMetadata);
|
|
2566
|
+
// player.addEventListener(wmcEvents.AMC_EVENT_PLAYER_SEGMENT_PLAYBACK, onNewSegmentForMetadata);
|
|
2567
|
+
// }
|
|
2568
|
+
// }
|
|
2569
|
+
|
|
2570
|
+
|
|
2571
|
+
|
|
2572
|
+
|
|
2573
|
+
let tcTranslator = BB.Utils.TimeCodesFramesTranslator;
|
|
2574
|
+
let sourceFPS = BB.Utils.getSourceFPS(options.frameRateMode);
|
|
2575
|
+
let mediaTimeFPS = BB.Utils.getMediaTimeFPS(options.frameRateMode);
|
|
2576
|
+
refreshRate = options.refreshRate;
|
|
2577
|
+
//augmentation
|
|
2578
|
+
let metaDataFetcher = null;
|
|
2579
|
+
let composer = null;
|
|
2580
|
+
let ctxGl = null;
|
|
2581
|
+
if (options.renderingMode == BB.RenderingMode.Augment) {
|
|
2582
|
+
//We load it all only when we know the game id
|
|
2583
|
+
/*
|
|
2584
|
+
let playerCont = video.parentElement;
|
|
2585
|
+
composer = new BB.Composer3d(playerCont,video,options);
|
|
2586
|
+
ads = BB.Ads(composer, options);
|
|
2587
|
+
metaDataFetcher = BB.MetaDataFetcher(options,composer);
|
|
2588
|
+
*/
|
|
2589
|
+
}
|
|
2590
|
+
else
|
|
2591
|
+
{
|
|
2592
|
+
//add a gl canvas to draw digits/timecode
|
|
2593
|
+
let canvas = document.createElement("canvas");
|
|
2594
|
+
canvas.width = 500;//video.videoWidth;
|
|
2595
|
+
canvas.height = 500;//video.videoHeight;
|
|
2596
|
+
document.body.appendChild(canvas);
|
|
2597
|
+
ctxGl = canvas.getContext('webgl');
|
|
2598
|
+
}
|
|
2599
|
+
let platform = BB.Utils.Platform;
|
|
2600
|
+
|
|
2601
|
+
//GL rendering method:
|
|
2602
|
+
let lastVideoFrameRenderedInGL = -1;
|
|
2603
|
+
let lastCorrection = false;
|
|
2604
|
+
const render = (currentFrame, correction) => {
|
|
2605
|
+
const startTime = performance.now();
|
|
2606
|
+
//if (BB.Utils.getSourceFPS(options.frameRateMode) != 30000/1001){ //ignoring this check for 29.97 fps
|
|
2607
|
+
if ((lastVideoFrameRenderedInGL == currentFrame) && (correction == lastCorrection))
|
|
2608
|
+
{
|
|
2609
|
+
if (options.debug) console.BBlog("render(): lastVideoFrameRenderedInGL==currentFrame: " + currentFrame);
|
|
2610
|
+
return 0;
|
|
2611
|
+
}
|
|
2612
|
+
//}
|
|
2613
|
+
if ((!options.enabled)||(options.stopAugment)){
|
|
2614
|
+
composer.CleanGL();
|
|
2615
|
+
return 0;
|
|
2616
|
+
}
|
|
2617
|
+
|
|
2618
|
+
if (options.renderingMode == BB.RenderingMode.RenderDigits)
|
|
2619
|
+
renderDigit(ctxGl, currentFrame);
|
|
2620
|
+
else if (options.renderingMode == BB.RenderingMode.RenderTimecode) {
|
|
2621
|
+
if ((sourceFPS==60000/1001)||(sourceFPS==60))
|
|
2622
|
+
renderTimecode(ctxGl, tcTranslator.Frames2Timecode(currentFrame,sourceFPS,true),true);
|
|
2623
|
+
else if ((sourceFPS==30000/1001)||(sourceFPS==30))
|
|
2624
|
+
renderTimecode(ctxGl, tcTranslator.Frames2Timecode(currentFrame,sourceFPS,false),false);
|
|
2625
|
+
}
|
|
2626
|
+
else if (options.renderingMode == BB.RenderingMode.Augment)
|
|
2627
|
+
if ((sourceFPS==60000/1001)||(sourceFPS==60))
|
|
2628
|
+
composer.renderAugmentation(tcTranslator.printTimecode(tcTranslator.Frames2Timecode(currentFrame,sourceFPS,true)));
|
|
2629
|
+
else if ((sourceFPS==30000/1001)||(sourceFPS==30)){
|
|
2630
|
+
if(correction){
|
|
2631
|
+
composer.renderAugmentation(tcTranslator.printTimecode(tcTranslator.Frames2Timecode(currentFrame*2+1,sourceFPS*2,true),true)); //@alal
|
|
2632
|
+
} else {
|
|
2633
|
+
composer.renderAugmentation(tcTranslator.printTimecode(tcTranslator.Frames2Timecode(currentFrame,sourceFPS,false),false)); //@alal
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
lastVideoFrameRenderedInGL = currentFrame;
|
|
2637
|
+
lastCorrection = correction;
|
|
2638
|
+
const endTime = performance.now();
|
|
2639
|
+
return endTime - startTime;
|
|
2640
|
+
};
|
|
2641
|
+
|
|
2642
|
+
|
|
2643
|
+
|
|
2644
|
+
|
|
2645
|
+
|
|
2646
|
+
|
|
2647
|
+
//Windows/Android://////////////
|
|
2648
|
+
|
|
2649
|
+
|
|
2650
|
+
|
|
2651
|
+
|
|
2652
|
+
//------------------
|
|
2653
|
+
|
|
2654
|
+
let countVid = 0;
|
|
2655
|
+
let lastVidDateTime=0;
|
|
2656
|
+
let prevDeltaVid=32;
|
|
2657
|
+
let prevprevDeltaVid=32;
|
|
2658
|
+
let lastCalled = "";
|
|
2659
|
+
let lastOffset=0;
|
|
2660
|
+
let lastOffsetShown;
|
|
2661
|
+
let onFrameVideoWindows2 = function(now,metadata) {
|
|
2662
|
+
//console.log("VID");
|
|
2663
|
+
if (lastCalled=="ANI2")
|
|
2664
|
+
{
|
|
2665
|
+
if (options.debug) console.BBlog("This videoCallback can be incorrect. This time doing stuff in requestAnimation frame");
|
|
2666
|
+
video.requestVideoFrameCallback(onFrameVideoWindows2);
|
|
2667
|
+
return;
|
|
2668
|
+
}
|
|
2669
|
+
lastCalled = "VID";
|
|
2670
|
+
lastNow=now;
|
|
2671
|
+
//console.log("mediaTime-currentTime = " + ((metadata.mediaTime - curTime)*mediaTimeFPS).toFixed(4) +
|
|
2672
|
+
// " expected-now = " + ((metadata.expectedDisplayTime - now)/1000*mediaTimeFPS).toFixed(4));
|
|
2673
|
+
let dblFullFrame = ((metadata.mediaTime - options.syncPoint.playerTime)%(60*60*24))*mediaTimeFPS; // take time excluding days
|
|
2674
|
+
let timeFromLastSync = metadata.mediaTime - options.syncPoint.playerTime;
|
|
2675
|
+
lastFrameFromVideoCallback = Math.floor(dblFullFrame+0.1) + options.syncPoint.frame;
|
|
2676
|
+
if (options.debug) {
|
|
2677
|
+
var numberInput = document.getElementById("numberInput").value;
|
|
2678
|
+
var number = parseInt(numberInput);
|
|
2679
|
+
lastFrameFromVideoCallback+=number;
|
|
2680
|
+
}
|
|
2681
|
+
else
|
|
2682
|
+
{
|
|
2683
|
+
lastFrameFromVideoCallback+=1;
|
|
2684
|
+
}
|
|
2685
|
+
lastMediaTime = metadata.mediaTime;
|
|
2686
|
+
|
|
2687
|
+
let playerTime = video.currentTime;
|
|
2688
|
+
let DebugLine2 = document.getElementById("debugText2");
|
|
2689
|
+
var timeStr = "";
|
|
2690
|
+
if (BB.Utils.getSourceFPS(options.frameRateMode) == 60000/1001){
|
|
2691
|
+
timeStr=tcTranslator.printTimecode(tcTranslator.Frames2Timecode(lastFrameFromVideoCallback, sourceFPS,true),true,true);
|
|
2692
|
+
}else if (BB.Utils.getSourceFPS(options.frameRateMode) == 30000/1001){
|
|
2693
|
+
timeStr=tcTranslator.printTimecode(tcTranslator.Frames2Timecode(lastFrameFromVideoCallback, sourceFPS,false),false,false);
|
|
2694
|
+
}else{
|
|
2695
|
+
timeStr="NOT SUPPORTED";
|
|
2696
|
+
}
|
|
2697
|
+
var offsetStr = "";
|
|
2698
|
+
if (BB.Utils.getSourceFPS(options.frameRateMode) == 60000/1001){
|
|
2699
|
+
offsetStr=tcTranslator.printTimecode(tcTranslator.Frames2Timecode(options.augmentation.offset, sourceFPS,true),true,true);
|
|
2700
|
+
}else if (BB.Utils.getSourceFPS(options.frameRateMode) == 30000/1001){
|
|
2701
|
+
offsetStr=tcTranslator.printTimecode(tcTranslator.Frames2Timecode(options.augmentation.offset, sourceFPS,false),false,false);
|
|
2702
|
+
}else{
|
|
2703
|
+
offsetStr="NOT SUPPORTED";
|
|
2704
|
+
}
|
|
2705
|
+
let resultFrames = lastFrameFromVideoCallback - options.augmentation.offset;
|
|
2706
|
+
if (metaDataFetcher) metaDataFetcher.setLastFrame(resultFrames);
|
|
2707
|
+
var resultTimecodeStr = "";
|
|
2708
|
+
if (BB.Utils.getSourceFPS(options.frameRateMode) == 60000/1001){
|
|
2709
|
+
resultTimecodeStr=tcTranslator.printTimecode(tcTranslator.Frames2Timecode(resultFrames, sourceFPS,true),true,true);
|
|
2710
|
+
}else if (BB.Utils.getSourceFPS(options.frameRateMode) == 30000/1001){
|
|
2711
|
+
resultTimecodeStr=tcTranslator.printTimecode(tcTranslator.Frames2Timecode(resultFrames, sourceFPS,false),false,false);
|
|
2712
|
+
}else{
|
|
2713
|
+
resultTimecodeStr="NOT SUPPORTED";
|
|
2714
|
+
}
|
|
2715
|
+
let deltaVid = (Date.now()-lastVidDateTime);
|
|
2716
|
+
|
|
2717
|
+
let nowTime = new Date();
|
|
2718
|
+
let hours = String(nowTime.getHours()).padStart(2, '0');
|
|
2719
|
+
let minutes = String(nowTime.getMinutes()).padStart(2, '0');
|
|
2720
|
+
let seconds = String(nowTime.getSeconds()).padStart(2, '0');
|
|
2721
|
+
let milliseconds = String(nowTime.getMilliseconds()).padStart(3, '0');
|
|
2722
|
+
let formattedTime = `${hours}:${minutes}:${seconds}.${milliseconds}`;
|
|
2723
|
+
|
|
2724
|
+
if (options.debug) {
|
|
2725
|
+
if ((options.augmentation.offset != lastOffset) || (lastOffsetShown< 10*60))
|
|
2726
|
+
{
|
|
2727
|
+
// set something to print offset different color
|
|
2728
|
+
var offsetColor = "red";
|
|
2729
|
+
}
|
|
2730
|
+
else
|
|
2731
|
+
{
|
|
2732
|
+
var offsetColor = "black";
|
|
2733
|
+
}
|
|
2734
|
+
DebugLine2.innerHTML = "VID. "
|
|
2735
|
+
+ " Game: " + options.gameId
|
|
2736
|
+
+ ", Time Fired: " + formattedTime
|
|
2737
|
+
+ ", PlayerTime:" + metadata.mediaTime.toFixed(4)
|
|
2738
|
+
|
|
2739
|
+
|
|
2740
|
+
+ ", Stream Time-Code: " + timeStr
|
|
2741
|
+
//+ ", offset: " + options.augmentation.offset + "(" + offsetStr + ")"
|
|
2742
|
+
+ ", offset: <span style='color:" + offsetColor + "'>" + options.augmentation.offset + "(" + offsetStr + ")</span>"
|
|
2743
|
+
+ ", Brain Time-Code: " + resultTimecodeStr
|
|
2744
|
+
//+ ", count:" + countVid
|
|
2745
|
+
+ ", TimeFromLastSync:" + timeFromLastSync.toFixed(4)
|
|
2746
|
+
+ ", LastSyncPointTime:" + options.syncPoint.playerTime.toFixed(4)
|
|
2747
|
+
+ ", LastSyncPointFrame:" + options.syncPoint.frame
|
|
2748
|
+
+ ", LastSyncPoint:" + options.syncPoint.stringTime
|
|
2749
|
+
//+ ", deltaVid: " + deltaVid + " ms"
|
|
2750
|
+
//+ ", prevDeltaVid: " + prevDeltaVid + " ms"
|
|
2751
|
+
//+ ", prevprevDeltaVid: " + prevprevDeltaVid + " ms"
|
|
2752
|
+
//+ ", expected-now = " + (metadata.expectedDisplayTime - now).toFixed(4)
|
|
2753
|
+
+ " </br>";
|
|
2754
|
+
if (options.augmentation.offset != lastOffset)
|
|
2755
|
+
{
|
|
2756
|
+
lastOffsetShown=0;
|
|
2757
|
+
}
|
|
2758
|
+
lastOffset = options.augmentation.offset;
|
|
2759
|
+
lastOffsetShown++;
|
|
2760
|
+
}
|
|
2761
|
+
|
|
2762
|
+
countVid++;
|
|
2763
|
+
lastVidDateTime = Date.now();
|
|
2764
|
+
let plus05Correction = false;
|
|
2765
|
+
if (options.debug) {
|
|
2766
|
+
var checkbox_plus05Correction = document.getElementById('plus05CorrectionCheckbox');
|
|
2767
|
+
plus05Correction = checkbox_plus05Correction.checked;
|
|
2768
|
+
}
|
|
2769
|
+
let renderingDuration = 0;
|
|
2770
|
+
//if ((metadata.expectedDisplayTime - now)>5)
|
|
2771
|
+
if (BB.Utils.getSourceFPS(options.frameRateMode) == 30000/1001){
|
|
2772
|
+
let bottomBorder = 24;
|
|
2773
|
+
let topBorder = 33+(33-bottomBorder);
|
|
2774
|
+
if (((deltaVid<bottomBorder)&&(prevDeltaVid<topBorder)) || ((prevDeltaVid<bottomBorder)&&(deltaVid<topBorder)) || ((prevprevDeltaVid<bottomBorder)&&(prevDeltaVid<topBorder)&&(deltaVid<topBorder)) || (plus05Correction==true))
|
|
2775
|
+
{
|
|
2776
|
+
DebugLine2.innerHTML += " Setting (some) ms timer";
|
|
2777
|
+
setTimeout(function() {
|
|
2778
|
+
render(resultFrames,false);
|
|
2779
|
+
//video.requestVideoFrameCallback(onFrameVideoWindows2);
|
|
2780
|
+
}, 10);
|
|
2781
|
+
}
|
|
2782
|
+
else
|
|
2783
|
+
{
|
|
2784
|
+
render(resultFrames,false);
|
|
2785
|
+
}
|
|
2786
|
+
}
|
|
2787
|
+
else if (BB.Utils.getSourceFPS(options.frameRateMode) == 60000/1001)
|
|
2788
|
+
{
|
|
2789
|
+
renderingDuration = render(resultFrames,false);
|
|
2790
|
+
}
|
|
2791
|
+
else
|
|
2792
|
+
{
|
|
2793
|
+
console.BBlog("FPS not supported");
|
|
2794
|
+
}
|
|
2795
|
+
prevprevDeltaVid=prevDeltaVid;
|
|
2796
|
+
prevDeltaVid=deltaVid;
|
|
2797
|
+
if (options.debug)
|
|
2798
|
+
{
|
|
2799
|
+
console.BBlog("VID."
|
|
2800
|
+
+ " TimeFired: " + formattedTime
|
|
2801
|
+
+ ", rendering: " + timeStr
|
|
2802
|
+
+ ", took: " + renderingDuration.toFixed(4) + " ms"
|
|
2803
|
+
);
|
|
2804
|
+
}
|
|
2805
|
+
video.requestVideoFrameCallback(onFrameVideoWindows2);
|
|
2806
|
+
return;
|
|
2807
|
+
}
|
|
2808
|
+
|
|
2809
|
+
const onAnimatedFrameWindows2 = (timeStamp) => {
|
|
2810
|
+
let nowTime = new Date();
|
|
2811
|
+
let hours = String(nowTime.getHours()).padStart(2, '0');
|
|
2812
|
+
let minutes = String(nowTime.getMinutes()).padStart(2, '0');
|
|
2813
|
+
let seconds = String(nowTime.getSeconds()).padStart(2, '0');
|
|
2814
|
+
let milliseconds = String(nowTime.getMilliseconds()).padStart(3, '0');
|
|
2815
|
+
let formattedTime = `${hours}:${minutes}:${seconds}.${milliseconds}`;
|
|
2816
|
+
//console.log("ANI");
|
|
2817
|
+
if (video.paused) //doing nothing
|
|
2818
|
+
{
|
|
2819
|
+
window.requestAnimationFrame(onAnimatedFrameWindows2);
|
|
2820
|
+
return;
|
|
2821
|
+
}
|
|
2822
|
+
let DebugLine = document.getElementById("debugText");
|
|
2823
|
+
if (options.debug) DebugLine.innerHTML = "ANI. "
|
|
2824
|
+
+ " Time Fired: " + formattedTime;
|
|
2825
|
+
|
|
2826
|
+
if ((lastCalled=="ANI") || (lastCalled=="ANI2"))
|
|
2827
|
+
{
|
|
2828
|
+
|
|
2829
|
+
var timeStr = "";
|
|
2830
|
+
if (BB.Utils.getSourceFPS(options.frameRateMode) == 60000/1001){
|
|
2831
|
+
timeStr=tcTranslator.printTimecode(tcTranslator.Frames2Timecode(lastVideoFrameRenderedInGL+1, sourceFPS,true),true,true);
|
|
2832
|
+
}else if (BB.Utils.getSourceFPS(options.frameRateMode) == 30000/1001){
|
|
2833
|
+
timeStr=tcTranslator.printTimecode(tcTranslator.Frames2Timecode(lastVideoFrameRenderedInGL+1, sourceFPS,false),false,false);
|
|
2834
|
+
}else{
|
|
2835
|
+
timeStr="NOT SUPPORTED";
|
|
2836
|
+
}
|
|
2837
|
+
var offsetStr = "";
|
|
2838
|
+
if (BB.Utils.getSourceFPS(options.frameRateMode) == 60000/1001){
|
|
2839
|
+
offsetStr=tcTranslator.printTimecode(tcTranslator.Frames2Timecode(options.augmentation.offset, sourceFPS,true),true,true);
|
|
2840
|
+
}else if (BB.Utils.getSourceFPS(options.frameRateMode) == 30000/1001){
|
|
2841
|
+
offsetStr=tcTranslator.printTimecode(tcTranslator.Frames2Timecode(options.augmentation.offset, sourceFPS,false),false,false);
|
|
2842
|
+
}else{
|
|
2843
|
+
offsetStr="NOT SUPPORTED";
|
|
2844
|
+
}
|
|
2845
|
+
let resultFrames = lastVideoFrameRenderedInGL - options.augmentation.offset;
|
|
2846
|
+
var resultTimecodeStr = "";
|
|
2847
|
+
if (BB.Utils.getSourceFPS(options.frameRateMode) == 60000/1001){
|
|
2848
|
+
resultTimecodeStr=tcTranslator.printTimecode(tcTranslator.Frames2Timecode(resultFrames, sourceFPS,true),true,true);
|
|
2849
|
+
}else if (BB.Utils.getSourceFPS(options.frameRateMode) == 30000/1001){
|
|
2850
|
+
resultTimecodeStr=tcTranslator.printTimecode(tcTranslator.Frames2Timecode(resultFrames, sourceFPS,false),false,false);
|
|
2851
|
+
}else{
|
|
2852
|
+
resultTimecodeStr="NOT SUPPORTED";
|
|
2853
|
+
}
|
|
2854
|
+
if (options.debug)
|
|
2855
|
+
{
|
|
2856
|
+
console.BBlog("Probable videoFrameCallback was not fired. Apllying correction mechanism (" + timeStr + ")" + ", stage: " + lastCalled);
|
|
2857
|
+
DebugLine.innerHTML += " correction";
|
|
2858
|
+
}
|
|
2859
|
+
|
|
2860
|
+
let renderingDuration = render(lastVideoFrameRenderedInGL+1,false);
|
|
2861
|
+
if (options.debug) console.BBlog("ANI."
|
|
2862
|
+
+ " Time Fired: " + formattedTime
|
|
2863
|
+
+ ", correction"
|
|
2864
|
+
+ ", time: " + timeStr
|
|
2865
|
+
+ ", offset: " + options.augmentation.offset + "(" + offsetStr + ")"
|
|
2866
|
+
+ ", resultTime: " + resultTimecodeStr
|
|
2867
|
+
+ ", took: " + renderingDuration.toFixed(4) + " ms");
|
|
2868
|
+
if (lastCalled=="ANI")
|
|
2869
|
+
lastCalled = "ANI2";
|
|
2870
|
+
else if (lastCalled=="ANI2")
|
|
2871
|
+
lastCalled = "ANI";
|
|
2872
|
+
}
|
|
2873
|
+
else
|
|
2874
|
+
{
|
|
2875
|
+
//DebugLine.innerHTML = "ANI. ";
|
|
2876
|
+
if (options.debug) console.BBlog("ANI."
|
|
2877
|
+
+ " Time Fired: " + formattedTime);
|
|
2878
|
+
lastCalled = "ANI";
|
|
2879
|
+
}
|
|
2880
|
+
|
|
2881
|
+
|
|
2882
|
+
|
|
2883
|
+
window.requestAnimationFrame(onAnimatedFrameWindows2);
|
|
2884
|
+
}
|
|
2885
|
+
|
|
2886
|
+
|
|
2887
|
+
|
|
2888
|
+
//Apple://///////////////////////
|
|
2889
|
+
//default rounding error:
|
|
2890
|
+
let roundingError = options.frameRateMode == BB.FrameRateMode.Src59_MediaTime60 ? -0.04 : +0.05;
|
|
2891
|
+
//debug - delete it after
|
|
2892
|
+
let deltaCur=0;
|
|
2893
|
+
let lastMetadata={};
|
|
2894
|
+
let lastExpectedDisplayTime=0;
|
|
2895
|
+
let lastTimeStamp=0;
|
|
2896
|
+
let nowFromVideoFrame=0;
|
|
2897
|
+
//--------------
|
|
2898
|
+
let countAppleVideo = 0;
|
|
2899
|
+
let frame = 0;
|
|
2900
|
+
let lastMediaTime = -1;
|
|
2901
|
+
|
|
2902
|
+
let lastVIDDate=0;
|
|
2903
|
+
let forceSync=false;
|
|
2904
|
+
|
|
2905
|
+
let lastCorr=0;
|
|
2906
|
+
let prevCorr=0;
|
|
2907
|
+
|
|
2908
|
+
|
|
2909
|
+
|
|
2910
|
+
|
|
2911
|
+
|
|
2912
|
+
// const onFrameVideoApple3 = (now,metadata) => {
|
|
2913
|
+
// lastVIDDate=performance.now();
|
|
2914
|
+
// var frameDbl = metadata.mediaTime*mediaTimeFPS;
|
|
2915
|
+
// var frame = Math.floor(frameDbl);
|
|
2916
|
+
// var timeStr = "";
|
|
2917
|
+
// if (BB.Utils.getSourceFPS(options.frameRateMode) == 60000/1001){
|
|
2918
|
+
// timeStr=tcTranslator.printTimecode(tcTranslator.Frames2Timecode(frame, sourceFPS,true),true,true);
|
|
2919
|
+
// }else if (BB.Utils.getSourceFPS(options.frameRateMode) == 30000/1001){
|
|
2920
|
+
// timeStr=tcTranslator.printTimecode(tcTranslator.Frames2Timecode(frame, sourceFPS,false),false,false);
|
|
2921
|
+
// }else{
|
|
2922
|
+
// timeStr="NOT SUPPORTED";
|
|
2923
|
+
// }
|
|
2924
|
+
// let DebugLine2 = document.getElementById("debugText2");
|
|
2925
|
+
// DebugLine2.textContent = " dblFrame: " + frameDbl.toFixed(4)
|
|
2926
|
+
// + " Frame: " + frame
|
|
2927
|
+
// + " countAppleVideo: " + countAppleVideo
|
|
2928
|
+
// + " Time: " + timeStr;
|
|
2929
|
+
|
|
2930
|
+
|
|
2931
|
+
|
|
2932
|
+
// countAppleVideo++;
|
|
2933
|
+
// video.requestVideoFrameCallback(onFrameVideoApple3);
|
|
2934
|
+
// return;
|
|
2935
|
+
// }
|
|
2936
|
+
let correction=false;
|
|
2937
|
+
const onAnimatedFrameApple3 = (timeStamp) => {
|
|
2938
|
+
var number = 2;
|
|
2939
|
+
if (options.debug)
|
|
2940
|
+
{
|
|
2941
|
+
let DebugLine = document.getElementById("debugText");
|
|
2942
|
+
var numberInput = document.getElementById("numberInput").value;
|
|
2943
|
+
number = parseInt(numberInput);
|
|
2944
|
+
}
|
|
2945
|
+
var realFrame = 0;
|
|
2946
|
+
|
|
2947
|
+
var dblFrame = ((video.currentTime - options.syncPoint.playerTime)%(60*60*24))*mediaTimeFPS; // take time excluding days
|
|
2948
|
+
realFrame=Math.floor(dblFrame) + options.syncPoint.frame;
|
|
2949
|
+
|
|
2950
|
+
realFrame+=number;
|
|
2951
|
+
var fraction = dblFrame - Math.floor(dblFrame);
|
|
2952
|
+
if ((correction)||(fraction<0.226)) correction=true;
|
|
2953
|
+
if (fraction>0.8) correction=false;
|
|
2954
|
+
if(correction) realFrame--;
|
|
2955
|
+
var timeStr = "";
|
|
2956
|
+
if (BB.Utils.getSourceFPS(options.frameRateMode) == 60000/1001){
|
|
2957
|
+
timeStr=tcTranslator.printTimecode(tcTranslator.Frames2Timecode(realFrame, sourceFPS,true),true,true);
|
|
2958
|
+
}else if (BB.Utils.getSourceFPS(options.frameRateMode) == 30000/1001){
|
|
2959
|
+
timeStr=tcTranslator.printTimecode(tcTranslator.Frames2Timecode(realFrame, sourceFPS,false),false,false);
|
|
2960
|
+
}else{
|
|
2961
|
+
timeStr="NOT SUPPORTED";
|
|
2962
|
+
}
|
|
2963
|
+
var offsetStr = "";
|
|
2964
|
+
if (BB.Utils.getSourceFPS(options.frameRateMode) == 60000/1001){
|
|
2965
|
+
offsetStr=tcTranslator.printTimecode(tcTranslator.Frames2Timecode(options.augmentation.offset, sourceFPS,true),true,true);
|
|
2966
|
+
}else if (BB.Utils.getSourceFPS(options.frameRateMode) == 30000/1001){
|
|
2967
|
+
offsetStr=tcTranslator.printTimecode(tcTranslator.Frames2Timecode(options.augmentation.offset, sourceFPS,false),false,false);
|
|
2968
|
+
}else{
|
|
2969
|
+
offsetStr="NOT SUPPORTED";
|
|
2970
|
+
}
|
|
2971
|
+
let resultFrames = realFrame - options.augmentation.offset;
|
|
2972
|
+
// if (options.debug) DebugLine.textContent = " countAppleANI: " + countAppleANI
|
|
2973
|
+
// + " dblFrame: " + dblFrame.toFixed(4)
|
|
2974
|
+
// + " realFrame: " + realFrame
|
|
2975
|
+
// + ", offset: " + options.augmentation.offset + "(" + offsetStr + ")"
|
|
2976
|
+
// + ", resultTime: " + resultTimecodeStr
|
|
2977
|
+
// + " Time: " + timeStr
|
|
2978
|
+
// + " correction: " + correction;
|
|
2979
|
+
|
|
2980
|
+
|
|
2981
|
+
if (metaDataFetcher) metaDataFetcher.setLastFrame(resultFrames);
|
|
2982
|
+
render(resultFrames);
|
|
2983
|
+
countAppleANI++;
|
|
2984
|
+
// lastFrame=realFrame;
|
|
2985
|
+
// prevCorr=lastCorr;
|
|
2986
|
+
// lastCorr=corr;
|
|
2987
|
+
|
|
2988
|
+
window.requestAnimationFrame(onAnimatedFrameApple3);
|
|
2989
|
+
return;
|
|
2990
|
+
}
|
|
2991
|
+
//-----------------------------------------------
|
|
2992
|
+
const onFrameVideoApple = (now,metadata) => {
|
|
2993
|
+
//if((!playing)&&(lastMediaTime!=-1)&&(lastMediaTime!=metadata.mediaTime)) playing=true;
|
|
2994
|
+
|
|
2995
|
+
let frameDbl = metadata.mediaTime*mediaTimeFPS;
|
|
2996
|
+
var number = 0;
|
|
2997
|
+
if (options.debug)
|
|
2998
|
+
{
|
|
2999
|
+
var numberInput = document.getElementById("numberInput").value;
|
|
3000
|
+
number = parseInt(numberInput);
|
|
3001
|
+
}
|
|
3002
|
+
frameDbl+=number;
|
|
3003
|
+
frame = Math.floor(frameDbl);
|
|
3004
|
+
var timeStr = "";
|
|
3005
|
+
if (BB.Utils.getSourceFPS(options.frameRateMode) == 60000/1001){
|
|
3006
|
+
timeStr=tcTranslator.printTimecode(tcTranslator.Frames2Timecode(frame, sourceFPS,true),true,true);
|
|
3007
|
+
}else if (BB.Utils.getSourceFPS(options.frameRateMode) == 30000/1001){
|
|
3008
|
+
timeStr=tcTranslator.printTimecode(tcTranslator.Frames2Timecode(frame, sourceFPS,false),false,false);
|
|
3009
|
+
}else{
|
|
3010
|
+
timeStr="NOT SUPPORTED";
|
|
3011
|
+
}
|
|
3012
|
+
let DebugLine2 = document.getElementById("debugText2");
|
|
3013
|
+
DebugLine2.textContent = " dblFrame: " + frameDbl.toFixed(4)
|
|
3014
|
+
+ " Frame: " + frame
|
|
3015
|
+
+ " countAppleVideo: " + countAppleVideo
|
|
3016
|
+
+ " Time: " + timeStr;
|
|
3017
|
+
|
|
3018
|
+
|
|
3019
|
+
|
|
3020
|
+
countAppleVideo++;
|
|
3021
|
+
lastMediaTime=metadata.mediaTime;
|
|
3022
|
+
video.requestVideoFrameCallback(onFrameVideoApple);
|
|
3023
|
+
return;
|
|
3024
|
+
nowFromVideoFrame=now;
|
|
3025
|
+
lastMetadata = metadata;
|
|
3026
|
+
/*if(fpsGetter.howMany()<500)
|
|
3027
|
+
{
|
|
3028
|
+
fpsGetter.addTime(metadata.mediaTime);
|
|
3029
|
+
if((fpsGetter.howMany()>100)&& (fpsGetter.howMany() % 100 == 0)){
|
|
3030
|
+
let probableFPS = fpsGetter.getProbableFPS();
|
|
3031
|
+
console.log("Setting new media FPS " + probableFPS);
|
|
3032
|
+
BB.Utils.setMediaFPS(probableFPS, options);
|
|
3033
|
+
}
|
|
3034
|
+
}*/
|
|
3035
|
+
|
|
3036
|
+
//calculate delta between mediatime and current time
|
|
3037
|
+
let playerTime = video.currentTime;
|
|
3038
|
+
let delta = ((playerTime - metadata.mediaTime)*1000);
|
|
3039
|
+
//deltaCalc.add(delta);
|
|
3040
|
+
deltaCur=delta;
|
|
3041
|
+
video.requestVideoFrameCallback(onFrameVideoApple);
|
|
3042
|
+
|
|
3043
|
+
}
|
|
3044
|
+
|
|
3045
|
+
let renderOnNextFrame = -1;
|
|
3046
|
+
let lastDisplayTimeDelay = 9999;
|
|
3047
|
+
let countUnderCorrection = 0;
|
|
3048
|
+
let countAppleANI = 0;
|
|
3049
|
+
let lastFrame=0;
|
|
3050
|
+
|
|
3051
|
+
console.BBlog("Setting Callbacks");
|
|
3052
|
+
console.BBlog("Checking window and methods");
|
|
3053
|
+
if (typeof window !== 'undefined') {
|
|
3054
|
+
console.BBlog('The window object is defined. This script is running in a browser environment.');
|
|
3055
|
+
} else {
|
|
3056
|
+
console.BBlog('The window object is not defined. This script might not be running in a browser environment.');
|
|
3057
|
+
}
|
|
3058
|
+
|
|
3059
|
+
if ('requestAnimationFrame' in window) {
|
|
3060
|
+
console.BBlog('requestAnimationFrame is supported');
|
|
3061
|
+
} else {
|
|
3062
|
+
console.BBlog('requestAnimationFrame is not supported');
|
|
3063
|
+
}
|
|
3064
|
+
|
|
3065
|
+
if (typeof video !== 'undefined') {
|
|
3066
|
+
console.BBlog('Video object is defined. This script is running in a browser environment.');
|
|
3067
|
+
} else {
|
|
3068
|
+
console.BBlog('Video object is not defined. This script might not be running in a browser environment.');
|
|
3069
|
+
}
|
|
3070
|
+
|
|
3071
|
+
if ('requestVideoFrameCallback' in video) {
|
|
3072
|
+
console.BBlog('requestVideoFrameCallback is supported');
|
|
3073
|
+
} else {
|
|
3074
|
+
console.BBlog('requestVideoFrameCallback is not supported');
|
|
3075
|
+
}
|
|
3076
|
+
|
|
3077
|
+
if (platform.isWindows() || platform.isAndroid() || platform.isChromeMac()) {
|
|
3078
|
+
window.requestAnimationFrame(onAnimatedFrameWindows2);
|
|
3079
|
+
if (video.readyState >= 2) {
|
|
3080
|
+
console.BBlog("video element is already loaded. Setting up requestVideoFrameCallback");
|
|
3081
|
+
video.requestVideoFrameCallback(onFrameVideoWindows2);
|
|
3082
|
+
}
|
|
3083
|
+
else{
|
|
3084
|
+
console.BBlog("video element is not yet loaded. Setting up loadedmetadata event to wait until it's loaded");
|
|
3085
|
+
video.addEventListener('loadedmetadata', () => {
|
|
3086
|
+
video.requestVideoFrameCallback(onFrameVideoWindows2);
|
|
3087
|
+
});
|
|
3088
|
+
}
|
|
3089
|
+
} else if (platform.isSafariMac()) {
|
|
3090
|
+
window.requestAnimationFrame(onAnimatedFrameApple3);
|
|
3091
|
+
//video.requestVideoFrameCallback(onFrameVideoApple3);
|
|
3092
|
+
|
|
3093
|
+
}else {
|
|
3094
|
+
console.BBlog("Platform not supported");
|
|
3095
|
+
}
|
|
3096
|
+
console.BBlog("CallBacks are set");
|
|
3097
|
+
console.BBlog("Calculating ScreenFrameRate");
|
|
3098
|
+
|
|
3099
|
+
function getAverageRefreshRate() {
|
|
3100
|
+
return new Promise((resolve) => {
|
|
3101
|
+
let lastFrameTime = 0;
|
|
3102
|
+
const refreshRates = [];
|
|
3103
|
+
const numSamples = 20;
|
|
3104
|
+
|
|
3105
|
+
function calculateRefreshRate(timestamp) {
|
|
3106
|
+
if (lastFrameTime) {
|
|
3107
|
+
const delta = timestamp - lastFrameTime;
|
|
3108
|
+
const refreshRate = 1000 / delta;
|
|
3109
|
+
if (!(refreshRate === undefined))
|
|
3110
|
+
{
|
|
3111
|
+
if ((refreshRate>0)&&(refreshRate<200))
|
|
3112
|
+
refreshRates.push(refreshRate);
|
|
3113
|
+
}
|
|
3114
|
+
if (refreshRates.length === numSamples) {
|
|
3115
|
+
const averageRate = refreshRates.reduce((a, b) => a + b, 0) / numSamples;
|
|
3116
|
+
|
|
3117
|
+
resolve(Math.round(averageRate));
|
|
3118
|
+
return; // Stop after collecting the required number of samples
|
|
3119
|
+
}
|
|
3120
|
+
}
|
|
3121
|
+
lastFrameTime = timestamp;
|
|
3122
|
+
requestAnimationFrame(calculateRefreshRate);
|
|
3123
|
+
}
|
|
3124
|
+
|
|
3125
|
+
requestAnimationFrame(calculateRefreshRate);
|
|
3126
|
+
});
|
|
3127
|
+
}
|
|
3128
|
+
|
|
3129
|
+
// Example usage:
|
|
3130
|
+
getAverageRefreshRate().then((rate) => {
|
|
3131
|
+
options.ScreenFPS = rate;
|
|
3132
|
+
|
|
3133
|
+
if ((options.ScreenFPS > 70) || (options.ScreenFPS < 50))
|
|
3134
|
+
{
|
|
3135
|
+
console.BBerror(`Average refresh screen rate: ${rate} FPS, Aborting augmentation. Only 60FPS screens supported`);
|
|
3136
|
+
options.stopAugment = true;
|
|
3137
|
+
}
|
|
3138
|
+
else
|
|
3139
|
+
{
|
|
3140
|
+
console.BBlog(`Average refresh screen rate: ${rate} FPS, Continuing augmentation`);
|
|
3141
|
+
}
|
|
3142
|
+
|
|
3143
|
+
});
|
|
3144
|
+
const initializeComponents = () => {
|
|
3145
|
+
//We load it all only when we know the game id
|
|
3146
|
+
let playerCont = video.parentElement;
|
|
3147
|
+
composer = new BB.Composer3d(playerCont,video,options);
|
|
3148
|
+
ads = BB.Ads(composer, options);
|
|
3149
|
+
metaDataFetcher = BB.MetaDataFetcher(options,composer);
|
|
3150
|
+
}
|
|
3151
|
+
const getCanvas = () => {
|
|
3152
|
+
return composer.getCanvas();
|
|
3153
|
+
}
|
|
3154
|
+
const onNewSegmentInterface = (eventObj) =>
|
|
3155
|
+
{
|
|
3156
|
+
onNewSegmentForMetadata(eventObj);
|
|
3157
|
+
};
|
|
3158
|
+
const onNewMetadataInterface = (eventObj) =>
|
|
3159
|
+
{
|
|
3160
|
+
onNewMetadata(eventObj);
|
|
3161
|
+
};
|
|
3162
|
+
return {getCanvas, onNewSegmentInterface, onNewMetadataInterface, initializeComponents};
|
|
3163
|
+
};
|
|
3164
|
+
BB.Composer3d = class {
|
|
3165
|
+
constructor(videoContainer,video,options) {
|
|
3166
|
+
if (options.augmentation.renderTimecode) {
|
|
3167
|
+
this.digitImg = {};
|
|
3168
|
+
for (let i = 0; i < 10; ++i) {
|
|
3169
|
+
let str1 = i + ".1";
|
|
3170
|
+
let str2 = i + ".2";
|
|
3171
|
+
this.digitImg[str1] = this.CreateDigitImg(str1);
|
|
3172
|
+
this.digitImg[str2] = this.CreateDigitImg(str2);
|
|
3173
|
+
}
|
|
3174
|
+
}
|
|
3175
|
+
this.options = Object.assign({adLoopTime:5000,isAdLoop: false},options || {});
|
|
3176
|
+
this.sourceFPS = BB.Utils.getSourceFPS(this.options.frameRateMode);
|
|
3177
|
+
this.metadataArr = [];
|
|
3178
|
+
this.metadataMap = {};
|
|
3179
|
+
this.videoStarted = false;
|
|
3180
|
+
this.advertisementMap = {};
|
|
3181
|
+
this.ad2Url = {};//map an ad to its url
|
|
3182
|
+
this.videoCopier = null;
|
|
3183
|
+
//create a webgl canvas:
|
|
3184
|
+
this.canvas = document.createElement('canvas');
|
|
3185
|
+
this.ctx = this.canvas.getContext('webgl2', {premultipliedAlpha: false});
|
|
3186
|
+
if (this.ctx == null)
|
|
3187
|
+
{
|
|
3188
|
+
this.ctx = this.canvas.getContext('webgl', {premultipliedAlpha: false});
|
|
3189
|
+
this.webGLVersion = 1;
|
|
3190
|
+
}
|
|
3191
|
+
else
|
|
3192
|
+
{
|
|
3193
|
+
this.webGLVersion = 2;
|
|
3194
|
+
}
|
|
3195
|
+
console.log("Created webgl1 context: " + this.ctx + " version " + this.webGLVersion);//@oror
|
|
3196
|
+
|
|
3197
|
+
//place it on top of the video player:
|
|
3198
|
+
let oTop = videoContainer.offsetTop;
|
|
3199
|
+
let oLeft = videoContainer.offsetLeft;
|
|
3200
|
+
let videoTag = videoContainer.getElementsByTagName("video")[0];
|
|
3201
|
+
this.canvas.style.top = 0;
|
|
3202
|
+
this.canvas.style.left = 0;
|
|
3203
|
+
|
|
3204
|
+
// if (typeof wmcPlayer !='undefined')
|
|
3205
|
+
// {
|
|
3206
|
+
// this.canvas.style.top = 0;
|
|
3207
|
+
// this.canvas.style.left = 0;
|
|
3208
|
+
// }
|
|
3209
|
+
// else
|
|
3210
|
+
// {
|
|
3211
|
+
// this.canvas.style.top = oTop + "px";
|
|
3212
|
+
// this.canvas.style.left = oLeft + "px";
|
|
3213
|
+
// }
|
|
3214
|
+
|
|
3215
|
+
// if (videoTag.readyState >= 2)
|
|
3216
|
+
// {
|
|
3217
|
+
// this.canvas.style.top = videoTag.style.top;
|
|
3218
|
+
// this.canvas.style.left = videoTag.style.left;
|
|
3219
|
+
// }
|
|
3220
|
+
// else
|
|
3221
|
+
// {
|
|
3222
|
+
// videoTag.addEventListener('loadedmetadata', () => {
|
|
3223
|
+
// this.canvas.style.top = videoTag.offsetTop + "px";
|
|
3224
|
+
// this.canvas.style.left = videoTag.offsetLeft + "px";
|
|
3225
|
+
// });
|
|
3226
|
+
// }
|
|
3227
|
+
this.canvas.style.width = "100%";
|
|
3228
|
+
this.canvas.style.zIndex = 100000;
|
|
3229
|
+
this.canvas.width = videoContainer.clientWidth;
|
|
3230
|
+
this.canvas.height = videoContainer.clientHeight;
|
|
3231
|
+
this.canvas.style.position = "absolute";
|
|
3232
|
+
|
|
3233
|
+
let nextElement = videoTag.nextSibling;
|
|
3234
|
+
|
|
3235
|
+
if(nextElement)
|
|
3236
|
+
videoContainer.insertBefore( this.canvas,nextElement);
|
|
3237
|
+
else
|
|
3238
|
+
videoContainer.appendChild(this.canvas);
|
|
3239
|
+
this.canvas.style["pointer-events"] = "none";
|
|
3240
|
+
videoTag.addEventListener("click", (e) => {
|
|
3241
|
+
this.OnUserClickOnCanvas(e);
|
|
3242
|
+
});
|
|
3243
|
+
//this.canvas.addEventListener("dblclick", this.ToggleFullscreen);
|
|
3244
|
+
//let that = this;
|
|
3245
|
+
//this.canvas.addEventListener("mousedown", function(e) {
|
|
3246
|
+
//that.OnUserClickOnCanvas(e);
|
|
3247
|
+
//});
|
|
3248
|
+
this.InitGL();
|
|
3249
|
+
this.adTexture = null;//general ad texture
|
|
3250
|
+
this.place2AdTexture = {};//mapping specific places to their texture
|
|
3251
|
+
this.place2ad = {};//mapping which ads belong to whcih place
|
|
3252
|
+
this.ad2place = {};//other way around
|
|
3253
|
+
this.currentAds2BoundingRect = [];//array of current-frame ads to their bounding rect
|
|
3254
|
+
this.lastTimeReportedIncomingTC = 0;
|
|
3255
|
+
this.autoAddIndex = null;
|
|
3256
|
+
}
|
|
3257
|
+
|
|
3258
|
+
isPowerOf2(value) {
|
|
3259
|
+
return (value & (value - 1)) == 0;
|
|
3260
|
+
}
|
|
3261
|
+
CreateDigitImg(str) {
|
|
3262
|
+
// Create a canvas element
|
|
3263
|
+
let canvas = document.createElement('canvas');
|
|
3264
|
+
canvas.width = 50; // Set the width of the canvas
|
|
3265
|
+
canvas.height = 50; // Set the height of the canvas
|
|
3266
|
+
|
|
3267
|
+
// Get the 2D rendering context
|
|
3268
|
+
let ctx = canvas.getContext('2d');
|
|
3269
|
+
// Set the fill style to black
|
|
3270
|
+
ctx.fillStyle = 'black';
|
|
3271
|
+
|
|
3272
|
+
// Fill the entire canvas with black
|
|
3273
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
3274
|
+
|
|
3275
|
+
// Set the font properties
|
|
3276
|
+
ctx.font = '30px Arial';
|
|
3277
|
+
ctx.fillStyle = 'white';
|
|
3278
|
+
|
|
3279
|
+
// Draw the number on the canvas
|
|
3280
|
+
ctx.fillText(str, 10, 35); // Adjust the position as needed
|
|
3281
|
+
|
|
3282
|
+
|
|
3283
|
+
// Convert the canvas to a data URL
|
|
3284
|
+
let dataURL = canvas.toDataURL();
|
|
3285
|
+
|
|
3286
|
+
// Create an image element and set its source to the data URL
|
|
3287
|
+
let img = document.createElement('img');
|
|
3288
|
+
img.src = dataURL;
|
|
3289
|
+
|
|
3290
|
+
return img;
|
|
3291
|
+
}
|
|
3292
|
+
OnUserClickOnCanvas(e) {
|
|
3293
|
+
const rect = this.canvas.getBoundingClientRect();
|
|
3294
|
+
let x = event.clientX - rect.left;
|
|
3295
|
+
let y = event.clientY - rect.top;
|
|
3296
|
+
x = x/(rect.right - rect.left);
|
|
3297
|
+
y = y/(rect.bottom - rect.top);
|
|
3298
|
+
//console.log("CANVAS CLICKED ! x: " + x + " y: " + y);
|
|
3299
|
+
for (let currentAd of this.currentAds2BoundingRect) {
|
|
3300
|
+
if (currentAd.rect.l <= x && currentAd.rect.r >= x &&
|
|
3301
|
+
currentAd.rect.t <= y && currentAd.rect.b >= y)
|
|
3302
|
+
{
|
|
3303
|
+
let adNameWithoutThatStupidHashSign = currentAd.name.substring(1);
|
|
3304
|
+
if (this.ad2Url[adNameWithoutThatStupidHashSign]) {
|
|
3305
|
+
let urlToGo = this.ad2Url[adNameWithoutThatStupidHashSign];
|
|
3306
|
+
console.log("Clicked on ad: " + adNameWithoutThatStupidHashSign + " so should navigate to " + urlToGo + " !!!!!!!!!!!!!!!!!!!!!!");
|
|
3307
|
+
window.open(urlToGo, '_blank');
|
|
3308
|
+
}
|
|
3309
|
+
return;
|
|
3310
|
+
}
|
|
3311
|
+
}
|
|
3312
|
+
|
|
3313
|
+
}
|
|
3314
|
+
/*TexUnitForInjectoLogo(){
|
|
3315
|
+
return 0;
|
|
3316
|
+
}*/
|
|
3317
|
+
TexUnitForGenericAd(){
|
|
3318
|
+
return 0;
|
|
3319
|
+
}
|
|
3320
|
+
TexUnitForAdInPlace(place){
|
|
3321
|
+
let placesArr = Object.keys(this.place2ad);
|
|
3322
|
+
for (let i = 0; i < placesArr.length; ++i)
|
|
3323
|
+
{
|
|
3324
|
+
if (place == placesArr[i]) return i + 1;
|
|
3325
|
+
}
|
|
3326
|
+
return 0;
|
|
3327
|
+
}
|
|
3328
|
+
/*TexUnitForVideo() {
|
|
3329
|
+
let placesArr = Object.keys(this.place2ad);
|
|
3330
|
+
return 1 + placesArr.length;
|
|
3331
|
+
}*/
|
|
3332
|
+
TexUnitForPlaceHolder(placeHolder) {
|
|
3333
|
+
let placesArr = Object.keys(this.place2ad);
|
|
3334
|
+
for (let i = 0; i < placesArr.length; ++i)
|
|
3335
|
+
{
|
|
3336
|
+
let place = placesArr[i];//wall
|
|
3337
|
+
if (placeHolder.name.indexOf(place) >= 0) return i;// + 1;
|
|
3338
|
+
}
|
|
3339
|
+
return 0;
|
|
3340
|
+
|
|
3341
|
+
}
|
|
3342
|
+
AdNameForPlaceHolder(placeHolder) {
|
|
3343
|
+
let placesArr = Object.keys(this.place2ad);
|
|
3344
|
+
for (let i = 0; i < placesArr.length; ++i)
|
|
3345
|
+
{
|
|
3346
|
+
let place = placesArr[i];//wall
|
|
3347
|
+
if (placeHolder.name.indexOf(place) >= 0 && this.place2AdTexture && this.place2AdTexture[place] && this.place2AdTexture[place].name)
|
|
3348
|
+
{
|
|
3349
|
+
return this.place2AdTexture[place].name;
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
3352
|
+
return this.lastAdName;
|
|
3353
|
+
}
|
|
3354
|
+
TexUnitBaseForAlpha() {
|
|
3355
|
+
return 1 + Object.keys(this.place2ad).length;// + 1;
|
|
3356
|
+
}
|
|
3357
|
+
InitGL() {
|
|
3358
|
+
// setup GLSL this.program
|
|
3359
|
+
let newFragmentShaderSource = BB.GL.fragmentShaderSource;
|
|
3360
|
+
//FIX for MAC (it has bugs with transparensy with light and bright colors so didabling transparensy if alpha in shader is less then 0.8)
|
|
3361
|
+
if (BB.Utils.Platform.isSafariMac())
|
|
3362
|
+
newFragmentShaderSource = BB.GL.fragmentShaderSource.replace("if (alpha == 0.0)", "if (alpha < 0.8)");
|
|
3363
|
+
this.program = webglUtils.createProgramFromSources(this.ctx,
|
|
3364
|
+
this.webGLVersion == 2 ? [BB.GL.vertexShaderSource, newFragmentShaderSource] :
|
|
3365
|
+
[BB.GL.vertexShaderSourceV1, BB.GL.fragmentShaderSourceV1]);
|
|
3366
|
+
this.ctx.clearColor(0.0, 0.0, 0.0, 0.0);//Set clear color to black, fully opaque
|
|
3367
|
+
this.ctx.clear(this.ctx.COLOR_BUFFER_BIT);// Clear the color buffer with specified clear color
|
|
3368
|
+
this.ctx.useProgram(this.program);// Tell it to use our this.program (pair of shaders)
|
|
3369
|
+
this.counter = 0;
|
|
3370
|
+
this.CleanGL();
|
|
3371
|
+
}
|
|
3372
|
+
CleanGL() {
|
|
3373
|
+
this.canvas.style.display="none";
|
|
3374
|
+
this.alphaFound = 0;
|
|
3375
|
+
}
|
|
3376
|
+
GenerateTexture(
|
|
3377
|
+
imageData,
|
|
3378
|
+
textureUnit,
|
|
3379
|
+
uniformName,
|
|
3380
|
+
internalFormat,
|
|
3381
|
+
width,
|
|
3382
|
+
height,
|
|
3383
|
+
oneByteAlignment) {
|
|
3384
|
+
//void this.ctx.texImage2D(target, level, internalformat, width, height, border, format, type, ImageData source);
|
|
3385
|
+
if (oneByteAlignment) {
|
|
3386
|
+
this.ctx.pixelStorei(this.ctx.UNPACK_ALIGNMENT, 1);
|
|
3387
|
+
} else {
|
|
3388
|
+
this.ctx.pixelStorei(this.ctx.UNPACK_ALIGNMENT, 4);
|
|
3389
|
+
}
|
|
3390
|
+
this.ctx.pixelStorei(this.ctx.UNPACK_FLIP_Y_WEBGL, false);
|
|
3391
|
+
const texture = this.ctx.createTexture();
|
|
3392
|
+
this.ctx.activeTexture(this.ctx.TEXTURE0 + textureUnit);
|
|
3393
|
+
this.ctx.bindTexture(this.ctx.TEXTURE_2D, texture);
|
|
3394
|
+
|
|
3395
|
+
try {
|
|
3396
|
+
if (width && height) {
|
|
3397
|
+
this.ctx.texImage2D(this.ctx.TEXTURE_2D, 0, (internalFormat == null ? this.ctx.RGBA : internalFormat), width, height, 0, (internalFormat == null ? this.ctx.RGBA : internalFormat), this.ctx.UNSIGNED_BYTE, imageData);
|
|
3398
|
+
} else {
|
|
3399
|
+
this.ctx.texImage2D(this.ctx.TEXTURE_2D, 0, (internalFormat == null ? this.ctx.RGBA : internalFormat), this.ctx.RGBA, this.ctx.UNSIGNED_BYTE, imageData);
|
|
3400
|
+
}
|
|
3401
|
+
} catch (ex) {
|
|
3402
|
+
console.error(ex);
|
|
3403
|
+
return null;
|
|
3404
|
+
}
|
|
3405
|
+
if (this.webGLVersion == 1)
|
|
3406
|
+
{
|
|
3407
|
+
let widthToInspect = width || imageData.width;
|
|
3408
|
+
let heightToInspect = height || imageData.height;
|
|
3409
|
+
if (widthToInspect && this.isPowerOf2(widthToInspect) && heightToInspect && this.isPowerOf2(heightToInspect)) {
|
|
3410
|
+
// Yes, it's a power of 2. Generate mips.
|
|
3411
|
+
this.ctx.generateMipmap(this.ctx.TEXTURE_2D);
|
|
3412
|
+
} else {
|
|
3413
|
+
// No, it's not a power of 2. Turn off mips and set
|
|
3414
|
+
// wrapping to clamp to edge
|
|
3415
|
+
this.ctx.texParameteri(this.ctx.TEXTURE_2D, this.ctx.TEXTURE_WRAP_S, this.ctx.CLAMP_TO_EDGE);
|
|
3416
|
+
this.ctx.texParameteri(this.ctx.TEXTURE_2D, this.ctx.TEXTURE_WRAP_T, this.ctx.CLAMP_TO_EDGE);
|
|
3417
|
+
this.ctx.texParameteri(this.ctx.TEXTURE_2D, this.ctx.TEXTURE_MIN_FILTER, this.ctx.LINEAR);
|
|
3418
|
+
}
|
|
3419
|
+
}
|
|
3420
|
+
else
|
|
3421
|
+
{
|
|
3422
|
+
// Set the parameters so we can render any size image.
|
|
3423
|
+
this.ctx.texParameteri(this.ctx.TEXTURE_2D, this.ctx.TEXTURE_WRAP_S, this.ctx.CLAMP_TO_EDGE);
|
|
3424
|
+
this.ctx.texParameteri(this.ctx.TEXTURE_2D, this.ctx.TEXTURE_WRAP_T, this.ctx.CLAMP_TO_EDGE);
|
|
3425
|
+
this.ctx.texParameteri(this.ctx.TEXTURE_2D, this.ctx.TEXTURE_MIN_FILTER, this.ctx.LINEAR);
|
|
3426
|
+
this.ctx.texParameteri(this.ctx.TEXTURE_2D, this.ctx.TEXTURE_MAG_FILTER, this.ctx.LINEAR);
|
|
3427
|
+
}
|
|
3428
|
+
let location = this.ctx.getUniformLocation(this.program, uniformName);
|
|
3429
|
+
if (location) {
|
|
3430
|
+
this.ctx.uniform1i(location, textureUnit);
|
|
3431
|
+
}
|
|
3432
|
+
return texture;
|
|
3433
|
+
}
|
|
3434
|
+
_nextAdvertisement() {
|
|
3435
|
+
this.autoAddIndex = setTimeout(()=>{
|
|
3436
|
+
const ads = Object.keys(this.advertisementMap);
|
|
3437
|
+
if(ads.length){
|
|
3438
|
+
this.SelectAd(ads[Math.floor(Math.random() * ads.length)]);
|
|
3439
|
+
this._nextAdvertisement();
|
|
3440
|
+
}
|
|
3441
|
+
},this.options.adLoopTime);
|
|
3442
|
+
}
|
|
3443
|
+
AddAdvertisement(name, path, cb = null) {
|
|
3444
|
+
|
|
3445
|
+
if(name.indexOf(".json") > -1) {
|
|
3446
|
+
fetch(path).then(async res => {
|
|
3447
|
+
this.place2ad = await res.json();
|
|
3448
|
+
this.ad2place = {}
|
|
3449
|
+
//create also the reverse mapping
|
|
3450
|
+
let placesArr = Object.keys(this.place2ad);
|
|
3451
|
+
for (let i = 0; i < placesArr.length; ++i) {
|
|
3452
|
+
let place = placesArr[i];
|
|
3453
|
+
if (place == "urls")
|
|
3454
|
+
{
|
|
3455
|
+
this.ad2Url = this.place2ad[place];
|
|
3456
|
+
delete this.place2ad[place];
|
|
3457
|
+
continue;
|
|
3458
|
+
}
|
|
3459
|
+
let adsForThisPlace = this.place2ad[place];
|
|
3460
|
+
for (let j = 0; j < adsForThisPlace.length; ++j) {
|
|
3461
|
+
let ad = adsForThisPlace[j];
|
|
3462
|
+
this.ad2place["#" + ad] = place;
|
|
3463
|
+
}
|
|
3464
|
+
}
|
|
3465
|
+
if (Object.keys(this.place2ad).length > 0) {
|
|
3466
|
+
console.log("composer::AddAdvertisement: place-to-ads mapping: ", this.place2ad);
|
|
3467
|
+
console.log("composer::AddAdvertisement: ads-to-place mapping: ", this.ad2place);
|
|
3468
|
+
setTimeout(()=>{this.MakeSureAdPerPlace();}, 3000);//make sure its ad is in the right place once all ads are loaded
|
|
3469
|
+
}
|
|
3470
|
+
|
|
3471
|
+
});
|
|
3472
|
+
if (cb) cb();
|
|
3473
|
+
return;
|
|
3474
|
+
}
|
|
3475
|
+
const adImage = new Image();
|
|
3476
|
+
adImage.crossOrigin = "Anonymous";
|
|
3477
|
+
adImage.onload = () => {
|
|
3478
|
+
let isFirst = Object.keys(this.advertisementMap).length === 0;
|
|
3479
|
+
this.advertisementMap[name] = adImage;
|
|
3480
|
+
if (isFirst) {
|
|
3481
|
+
this.SelectAd(name);
|
|
3482
|
+
if(this.options.isAdLoop) {
|
|
3483
|
+
clearTimeout(this.autoAddIndex);
|
|
3484
|
+
this._nextAdvertisement();
|
|
3485
|
+
}
|
|
3486
|
+
}
|
|
3487
|
+
if (cb) cb();
|
|
3488
|
+
}
|
|
3489
|
+
adImage.onerror = () => {
|
|
3490
|
+
alert(`( Security ) We cant reach ${path} .`);
|
|
3491
|
+
}
|
|
3492
|
+
adImage.src = path;
|
|
3493
|
+
return adImage;
|
|
3494
|
+
}
|
|
3495
|
+
MakeSureAdPerPlace() {
|
|
3496
|
+
//in case we have places, make sure each place has its initial ad
|
|
3497
|
+
if (this.place2ad) {
|
|
3498
|
+
let placesArr = Object.keys(this.place2ad);
|
|
3499
|
+
for (let i = 0; i < placesArr.length; ++i) {
|
|
3500
|
+
let place = placesArr[i];
|
|
3501
|
+
//do we have a texture for that place ?
|
|
3502
|
+
if (!this.place2AdTexture[place] && this.place2ad[place].length > 0) {//no we don't, add one
|
|
3503
|
+
console.log("MakeSureAdPerPlace: Set default image " + this.place2ad[place][0] + " for place " + place);
|
|
3504
|
+
this.SelectAd("#" + this.place2ad[place][0]);
|
|
3505
|
+
}
|
|
3506
|
+
}
|
|
3507
|
+
}
|
|
3508
|
+
}
|
|
3509
|
+
SelectAd(name) {
|
|
3510
|
+
console.log("composer::SelectAd: selected ad is " + name);
|
|
3511
|
+
let adImage = this.advertisementMap[name];
|
|
3512
|
+
if (!adImage) {
|
|
3513
|
+
console.error("SelectAd: Failed to find image '" + name + "'");
|
|
3514
|
+
} else {
|
|
3515
|
+
//is it for specific place ?
|
|
3516
|
+
if (this.ad2place && this.ad2place[name]) {//ad for a particular place
|
|
3517
|
+
let place = this.ad2place[name];
|
|
3518
|
+
console.log("composer::SelectAd: The selected ad should be in place " + place);
|
|
3519
|
+
let texUnitId = this.TexUnitForAdInPlace(place);
|
|
3520
|
+
let texName = "ad" + (texUnitId-1);
|
|
3521
|
+
if (this.place2AdTexture[place]) this.ctx.deleteTexture(this.place2AdTexture[place].tex);
|
|
3522
|
+
this.place2AdTexture[place] =
|
|
3523
|
+
{
|
|
3524
|
+
tex:this.GenerateTexture(adImage, texUnitId, texName),
|
|
3525
|
+
texUnitId:texUnitId,
|
|
3526
|
+
texName:texName,
|
|
3527
|
+
name:name
|
|
3528
|
+
};
|
|
3529
|
+
|
|
3530
|
+
}
|
|
3531
|
+
else {//general ad
|
|
3532
|
+
if (this.adTexture != null) {
|
|
3533
|
+
this.ctx.deleteTexture(this.adTexture)
|
|
3534
|
+
}
|
|
3535
|
+
this.adTexture = this.GenerateTexture(adImage, this.TexUnitForGenericAd(), "ad0");
|
|
3536
|
+
this.lastAd = adImage;
|
|
3537
|
+
this.lastAdName = name;
|
|
3538
|
+
}
|
|
3539
|
+
}
|
|
3540
|
+
};
|
|
3541
|
+
|
|
3542
|
+
OnMetaData(frameMetaData) {
|
|
3543
|
+
//console.BBlog("OnMetaData " + frameMetaData.timecode);
|
|
3544
|
+
this.metadataMap[frameMetaData.timecode] = frameMetaData;
|
|
3545
|
+
this.metadataArr.push(frameMetaData.timecode);
|
|
3546
|
+
}
|
|
3547
|
+
|
|
3548
|
+
ToggleFullscreen () {
|
|
3549
|
+
(this.requestFullscreen || this.webkitRequestFullscreen).call(this).catch((err) => {
|
|
3550
|
+
console.error(err)
|
|
3551
|
+
});
|
|
3552
|
+
}
|
|
3553
|
+
|
|
3554
|
+
RenderFrame() {
|
|
3555
|
+
// look up where the vertex data needs to go.
|
|
3556
|
+
//let texCoordAttributeLocation = this.ctx.getAttribLocation(this.program, "a_texCoord");
|
|
3557
|
+
let positionAttributeLocation = this.ctx.getAttribLocation(this.program, "a_position");
|
|
3558
|
+
|
|
3559
|
+
|
|
3560
|
+
// set the resolution for both vertex and fragment shaders
|
|
3561
|
+
let resolutionLocation = this.ctx.getUniformLocation(this.program, "u_resolution");
|
|
3562
|
+
let resolutionLocation2 = this.ctx.getUniformLocation(this.program, "u_resolution2");
|
|
3563
|
+
this.ctx.uniform2f(resolutionLocation, this.ctx.canvas.width, this.ctx.canvas.height);
|
|
3564
|
+
this.ctx.uniform2f(resolutionLocation2, this.ctx.canvas.width, this.ctx.canvas.height);
|
|
3565
|
+
|
|
3566
|
+
//shader matrix (unity):
|
|
3567
|
+
if (this.webGLVersion == 2)
|
|
3568
|
+
{
|
|
3569
|
+
let matrixLocation = this.ctx.getUniformLocation(this.program, "u_matrix");
|
|
3570
|
+
let matrix = [
|
|
3571
|
+
1, 0, 0,
|
|
3572
|
+
0, 1, 0,
|
|
3573
|
+
0, 0, 1
|
|
3574
|
+
];
|
|
3575
|
+
this.ctx.uniformMatrix3fv(matrixLocation, false, matrix);
|
|
3576
|
+
}
|
|
3577
|
+
|
|
3578
|
+
let vao = null;
|
|
3579
|
+
if (this.webGLVersion == 2)
|
|
3580
|
+
{
|
|
3581
|
+
// Create a vertex array object (attribute state)
|
|
3582
|
+
vao = this.ctx.createVertexArray();
|
|
3583
|
+
|
|
3584
|
+
// and make it the one we're currently working with
|
|
3585
|
+
this.ctx.bindVertexArray(vao);
|
|
3586
|
+
}
|
|
3587
|
+
|
|
3588
|
+
// Create a buffer and put a single pixel space rectangle in
|
|
3589
|
+
// it (2 triangles)
|
|
3590
|
+
let positionBuffer = this.ctx.createBuffer();
|
|
3591
|
+
|
|
3592
|
+
// Turn on the attribute
|
|
3593
|
+
this.ctx.enableVertexAttribArray(positionAttributeLocation);
|
|
3594
|
+
|
|
3595
|
+
// Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer)
|
|
3596
|
+
this.ctx.bindBuffer(this.ctx.ARRAY_BUFFER, positionBuffer);
|
|
3597
|
+
|
|
3598
|
+
// Tell the attribute how to get data out of positionBuffer (ARRAY_BUFFER)
|
|
3599
|
+
let size = 2; // 2 components per iteration
|
|
3600
|
+
let type = this.ctx.FLOAT; // the data is 32bit floats
|
|
3601
|
+
let normalize = false; // don't normalize the data
|
|
3602
|
+
let stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
|
|
3603
|
+
let offset = 0; // start at the beginning of the buffer
|
|
3604
|
+
this.ctx.vertexAttribPointer(
|
|
3605
|
+
positionAttributeLocation, size, type, normalize, stride, offset);
|
|
3606
|
+
|
|
3607
|
+
// provide texture coordinates for the rectangle.
|
|
3608
|
+
let texCoordBuffer = this.ctx.createBuffer();
|
|
3609
|
+
this.ctx.bindBuffer(this.ctx.ARRAY_BUFFER, texCoordBuffer);
|
|
3610
|
+
this.ctx.bufferData(this.ctx.ARRAY_BUFFER, new Float32Array([
|
|
3611
|
+
0.0, 0.0,
|
|
3612
|
+
1.0, 0.0,
|
|
3613
|
+
0.0, 1.0,
|
|
3614
|
+
0.0, 1.0,
|
|
3615
|
+
1.0, 0.0,
|
|
3616
|
+
1.0, 1.0,
|
|
3617
|
+
]), this.ctx.STATIC_DRAW);
|
|
3618
|
+
|
|
3619
|
+
|
|
3620
|
+
/*
|
|
3621
|
+
// Turn on the attribute
|
|
3622
|
+
this.ctx.enableVertexAttribArray(texCoordAttributeLocation);
|
|
3623
|
+
|
|
3624
|
+
// Tell the attribute how to get data out of texCoordBuffer (ARRAY_BUFFER)
|
|
3625
|
+
let size = 2; // 2 components per iteration
|
|
3626
|
+
let type = this.ctx.FLOAT; // the data is 32bit floats
|
|
3627
|
+
let normalize = false; // don't normalize the data
|
|
3628
|
+
let stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
|
|
3629
|
+
let offset = 0; // start at the beginning of the buffer
|
|
3630
|
+
this.ctx.vertexAttribPointer(
|
|
3631
|
+
texCoordAttributeLocation, size, type, normalize, stride, offset);
|
|
3632
|
+
*/
|
|
3633
|
+
//webglUtils.resizeCanvasToDisplaySize(this.ctx.canvas);
|
|
3634
|
+
|
|
3635
|
+
// Tell WebGL how to convert from clip space to pixels
|
|
3636
|
+
this.ctx.viewport(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
|
|
3637
|
+
|
|
3638
|
+
// Clear the canvas
|
|
3639
|
+
this.ctx.clearColor(0, 0, 0, 0);
|
|
3640
|
+
this.ctx.clear(this.ctx.COLOR_BUFFER_BIT | this.ctx.DEPTH_BUFFER_BIT);
|
|
3641
|
+
|
|
3642
|
+
// Bind the attribute/buffer set we want.
|
|
3643
|
+
//this.ctx.bindVertexArray(vao);
|
|
3644
|
+
|
|
3645
|
+
|
|
3646
|
+
// Bind the position buffer so this.ctx.bufferData that will be called
|
|
3647
|
+
// in setRectangle puts data in the position buffer
|
|
3648
|
+
this.ctx.bindBuffer(this.ctx.ARRAY_BUFFER, positionBuffer);
|
|
3649
|
+
|
|
3650
|
+
// Set a rectangle the same size as the image.
|
|
3651
|
+
this.SetRectangle(this.ctx, 0, 0, this.canvas.width, this.canvas.height);
|
|
3652
|
+
|
|
3653
|
+
// Draw the rectangle.
|
|
3654
|
+
let primitiveType = this.ctx.TRIANGLES;
|
|
3655
|
+
//let offset = 0;
|
|
3656
|
+
let count = 6;
|
|
3657
|
+
this.ctx.drawArrays(primitiveType, offset, count);
|
|
3658
|
+
}
|
|
3659
|
+
|
|
3660
|
+
SetRectangle(gl, x, y, width, height) {
|
|
3661
|
+
let x1 = x;
|
|
3662
|
+
let x2 = x + width;
|
|
3663
|
+
let y1 = y;
|
|
3664
|
+
let y2 = y + height;
|
|
3665
|
+
this.ctx.bufferData(this.ctx.ARRAY_BUFFER, new Float32Array([
|
|
3666
|
+
x1, y1,
|
|
3667
|
+
x2, y1,
|
|
3668
|
+
x1, y2,
|
|
3669
|
+
x1, y2,
|
|
3670
|
+
x2, y1,
|
|
3671
|
+
x2, y2,
|
|
3672
|
+
]), this.ctx.STATIC_DRAW);
|
|
3673
|
+
}
|
|
3674
|
+
|
|
3675
|
+
renderAugmentation(nextTimeCode) {
|
|
3676
|
+
let tcTranslator = BB.Utils.TimeCodesFramesTranslator;
|
|
3677
|
+
//console.log("nextTimeCode: " + nextTimeCode);//@oror "00:00:14:02.1"
|
|
3678
|
+
let tc = tcTranslator.timeCodeStr2TimeCodeArray(nextTimeCode);
|
|
3679
|
+
let tcPlus1 = tcTranslator.TimeCodeAddFrames(tc, 1, this.sourceFPS);
|
|
3680
|
+
let tcPlus2 = tcTranslator.TimeCodeAddFrames(tc, 2, this.sourceFPS);
|
|
3681
|
+
let tcPlus1Str = tcTranslator.printTimecode(tcPlus1);
|
|
3682
|
+
let tcPlus2Str = tcTranslator.printTimecode(tcPlus2);
|
|
3683
|
+
|
|
3684
|
+
|
|
3685
|
+
this.videoStarted = true;
|
|
3686
|
+
const t1 = performance.now();
|
|
3687
|
+
|
|
3688
|
+
|
|
3689
|
+
//clean all the earliest meta:
|
|
3690
|
+
/*
|
|
3691
|
+
while (this.metadataArr.length > 0 &&
|
|
3692
|
+
this.metadataArr[0] < nextTimeCode) {
|
|
3693
|
+
let toDeleteFrameMetaData = this.metadataMap[this.metadataArr[0]];
|
|
3694
|
+
for (const key in toDeleteFrameMetaData.placeholders) {
|
|
3695
|
+
let holderToDelete = toDeleteFrameMetaData.placeholders[key];
|
|
3696
|
+
if (holderToDelete.alphaAsUint8Array) {
|
|
3697
|
+
holderToDelete.alphaAsUint8Array = null;
|
|
3698
|
+
}
|
|
3699
|
+
}
|
|
3700
|
+
delete this.metadataArr[this.metadataArr[0]];
|
|
3701
|
+
this.metadataArr.shift();
|
|
3702
|
+
}
|
|
3703
|
+
*/
|
|
3704
|
+
|
|
3705
|
+
//get the next meta frame
|
|
3706
|
+
let frameMetaData = null;
|
|
3707
|
+
let next = [];
|
|
3708
|
+
if (this.metadataMap[nextTimeCode]) {
|
|
3709
|
+
frameMetaData = this.metadataMap[nextTimeCode];
|
|
3710
|
+
/*delete this.metadataMap[nextTimeCode];
|
|
3711
|
+
if (this.sourceFPS < 40) {
|
|
3712
|
+
delete this.metadataMap[nextTimeCode + ".1"];
|
|
3713
|
+
}*/
|
|
3714
|
+
|
|
3715
|
+
if (this.metadataMap[tcPlus1Str]) {
|
|
3716
|
+
next.push({nextVideo:this.metadataMap[tcPlus1Str],nextTimeCode:tcPlus1Str});
|
|
3717
|
+
}
|
|
3718
|
+
if (this.metadataMap[tcPlus2Str]) {
|
|
3719
|
+
next.push({nextVideo:this.metadataMap[tcPlus2Str],nextTimeCode:tcPlus2Str});
|
|
3720
|
+
}
|
|
3721
|
+
const metaArrIndex = this.metadataArr.indexOf(nextTimeCode);
|
|
3722
|
+
|
|
3723
|
+
//if (metaArrIndex > -1) this.metadataArr.splice(metaArrIndex, 1); //was
|
|
3724
|
+
if (metaArrIndex > -5) this.metadataArr.splice(0, metaArrIndex-4).forEach(item => {
|
|
3725
|
+
delete this.metadataMap[item];
|
|
3726
|
+
});
|
|
3727
|
+
this.alphaFound++;
|
|
3728
|
+
}
|
|
3729
|
+
else {
|
|
3730
|
+
this.counter++;
|
|
3731
|
+
if(this.counter > 60)
|
|
3732
|
+
{
|
|
3733
|
+
this.counter = 0;
|
|
3734
|
+
console.error("Missing meta data at time code " + nextTimeCode);
|
|
3735
|
+
}
|
|
3736
|
+
this.alphaFound++; //test for drawing number always
|
|
3737
|
+
//this.alphaFound = 0;
|
|
3738
|
+
////nextImageData.buffer = null;
|
|
3739
|
+
//return;
|
|
3740
|
+
}
|
|
3741
|
+
if (this.alphaFound > 30 && this.canvas.style.display == "none") {
|
|
3742
|
+
this.canvas.style.display = "";
|
|
3743
|
+
}
|
|
3744
|
+
|
|
3745
|
+
let texturesForThisFrame = [];
|
|
3746
|
+
this.canvas.width = 1280;
|
|
3747
|
+
this.canvas.height = 720;
|
|
3748
|
+
|
|
3749
|
+
let placeHolderIndex = 0;
|
|
3750
|
+
this.currentAds2BoundingRect = [];//reset the coordinates
|
|
3751
|
+
if (frameMetaData) {
|
|
3752
|
+
//set the canvas size according the engine's canvas size
|
|
3753
|
+
// if (frameMetaData.canvasSize && (
|
|
3754
|
+
// frameMetaData.canvasSize.width != this.ctx.canvas.width ||
|
|
3755
|
+
// frameMetaData.canvasSize.height != this.ctx.canvas.height)) {
|
|
3756
|
+
// this.ctx.canvas.width = frameMetaData.canvasSize.width;
|
|
3757
|
+
// this.ctx.canvas.height = frameMetaData.canvasSize.height;
|
|
3758
|
+
// }
|
|
3759
|
+
|
|
3760
|
+
//for each place-holder, create an alpha texture and inform the shader of the alpha mate's position and size
|
|
3761
|
+
//if (false)
|
|
3762
|
+
let doNotRenderAugmentation = false;
|
|
3763
|
+
if (this.options.debug)
|
|
3764
|
+
{
|
|
3765
|
+
var checkbox_doNotRenderAugmentation = document.getElementById('doNotRenderAugmentationCheckbox');
|
|
3766
|
+
doNotRenderAugmentation = checkbox_doNotRenderAugmentation.checked;
|
|
3767
|
+
}
|
|
3768
|
+
if(!doNotRenderAugmentation)
|
|
3769
|
+
for (const key in frameMetaData.placeholders) {
|
|
3770
|
+
const holder = frameMetaData.placeholders[key];
|
|
3771
|
+
if (!holder.alphaAsUint8Array) continue;
|
|
3772
|
+
//create the alpha texture
|
|
3773
|
+
let alphaUnfiromName = "alpha" + placeHolderIndex;
|
|
3774
|
+
let alphaTextureNum = this.TexUnitBaseForAlpha() + placeHolderIndex;
|
|
3775
|
+
holder.alphaTex = this.GenerateTexture(holder.alphaAsUint8Array,
|
|
3776
|
+
alphaTextureNum,
|
|
3777
|
+
alphaUnfiromName,
|
|
3778
|
+
this.ctx.ALPHA,
|
|
3779
|
+
holder.width,
|
|
3780
|
+
holder.height,
|
|
3781
|
+
true);
|
|
3782
|
+
holder.alphaAsUint8Array = null;
|
|
3783
|
+
if (!holder.alphaTex) {
|
|
3784
|
+
console.error("Failed to create alpha texture");
|
|
3785
|
+
continue;
|
|
3786
|
+
}
|
|
3787
|
+
texturesForThisFrame.push(holder.alphaTex);
|
|
3788
|
+
//notify the shader that this place-holder exist
|
|
3789
|
+
let hasLogoUniformName = "hasLogo" + placeHolderIndex;
|
|
3790
|
+
let hasLogoLoc = this.ctx.getUniformLocation(this.program, hasLogoUniformName);
|
|
3791
|
+
this.ctx.uniform1i(hasLogoLoc, 1);
|
|
3792
|
+
//tell the shader whcih add to use for this placeholder
|
|
3793
|
+
let adForLogoUniformName = "adForLogo" + placeHolderIndex;
|
|
3794
|
+
let adForLogoUniform = this.ctx.getUniformLocation(this.program, adForLogoUniformName);
|
|
3795
|
+
let isBlur = false;
|
|
3796
|
+
if (holder.name.indexOf("blur") >= 0)
|
|
3797
|
+
{
|
|
3798
|
+
this.ctx.uniform1i(adForLogoUniform, 99);
|
|
3799
|
+
isBlur = true;
|
|
3800
|
+
}
|
|
3801
|
+
else
|
|
3802
|
+
{
|
|
3803
|
+
this.ctx.uniform1i(adForLogoUniform, this.TexUnitForPlaceHolder(holder));
|
|
3804
|
+
}
|
|
3805
|
+
|
|
3806
|
+
//set that alpha top-left and size
|
|
3807
|
+
let alphaTopLeftUniformName = "u_alpha" + placeHolderIndex + "TopLeft";
|
|
3808
|
+
let alphaTopLeftLoc = this.ctx.getUniformLocation(this.program, alphaTopLeftUniformName);
|
|
3809
|
+
this.ctx.uniform2fv(alphaTopLeftLoc, [holder.maskX, holder.maskY]);
|
|
3810
|
+
let alphaSizeUniformName = "u_alpha" + placeHolderIndex + "Size";
|
|
3811
|
+
let alphaSizeLoc = this.ctx.getUniformLocation(this.program, alphaSizeUniformName);
|
|
3812
|
+
this.ctx.uniform2fv(alphaSizeLoc, [holder.width, holder.height]);
|
|
3813
|
+
//homography:
|
|
3814
|
+
let homographyMatrixUniformName = "u_matrix" + placeHolderIndex;
|
|
3815
|
+
let placeHolderHomographyLocation = this.ctx.getUniformLocation(this.program, homographyMatrixUniformName);
|
|
3816
|
+
this.ctx.uniformMatrix3fv(placeHolderHomographyLocation, false, holder.tranform);
|
|
3817
|
+
//bounding rect:
|
|
3818
|
+
if (!isBlur)
|
|
3819
|
+
{
|
|
3820
|
+
let boundingRect = {l:-1,t:-1,r:-1,b:-1};
|
|
3821
|
+
for (let corner of holder.corners) {
|
|
3822
|
+
if (corner.x < boundingRect.l ||boundingRect.l == -1) boundingRect.l = corner.x;
|
|
3823
|
+
if (corner.x > boundingRect.r ||boundingRect.r == -1) boundingRect.r = corner.x;
|
|
3824
|
+
if (corner.y < boundingRect.t ||boundingRect.t == -1) boundingRect.t = corner.y;
|
|
3825
|
+
if (corner.y > boundingRect.b ||boundingRect.b == -1) boundingRect.b = corner.y;
|
|
3826
|
+
}
|
|
3827
|
+
boundingRect.l /= frameMetaData.canvasSize.width;
|
|
3828
|
+
boundingRect.r /= frameMetaData.canvasSize.width;
|
|
3829
|
+
boundingRect.t /= frameMetaData.canvasSize.height;
|
|
3830
|
+
boundingRect.b /= frameMetaData.canvasSize.height;
|
|
3831
|
+
this.currentAds2BoundingRect.push({
|
|
3832
|
+
name:this.AdNameForPlaceHolder(holder),
|
|
3833
|
+
rect:boundingRect
|
|
3834
|
+
});
|
|
3835
|
+
}
|
|
3836
|
+
/*
|
|
3837
|
+
//This code create the perspective transform in JS
|
|
3838
|
+
//Currently we use the transformation supplied with the frameMetaData instead
|
|
3839
|
+
let srcCorners = [
|
|
3840
|
+
0, 0,
|
|
3841
|
+
this.canvas.width, 0,
|
|
3842
|
+
this.canvas.width, this.canvas.height,
|
|
3843
|
+
0, this.canvas.height];
|
|
3844
|
+
|
|
3845
|
+
let dstCorners = [];
|
|
3846
|
+
let ratioX = this.canvas.width / frameMetaData.processingWidth;
|
|
3847
|
+
let ratioY = this.canvas.height / frameMetaData.processingHeight;
|
|
3848
|
+
for (let corner of holder.corners) {
|
|
3849
|
+
corner.x *= ratioX;
|
|
3850
|
+
corner.y *= ratioY;
|
|
3851
|
+
dstCorners[dstCorners.length] = corner.x;
|
|
3852
|
+
dstCorners[dstCorners.length] = corner.y;
|
|
3853
|
+
}
|
|
3854
|
+
|
|
3855
|
+
let perspT = PerspT(srcCorners, dstCorners);
|
|
3856
|
+
this.ctx.uniformMatrix3fv(placeHolderHomographyLocation, false, perspT.coeffsInv);
|
|
3857
|
+
*/
|
|
3858
|
+
placeHolderIndex++;
|
|
3859
|
+
}
|
|
3860
|
+
} else {
|
|
3861
|
+
// console.log(`No data at ${tc}`);
|
|
3862
|
+
}
|
|
3863
|
+
|
|
3864
|
+
//mark the other logo as "not exist"
|
|
3865
|
+
for (; placeHolderIndex < BB.MAX_LOGOS; ++placeHolderIndex) {
|
|
3866
|
+
let hasLogoUniformName = "hasLogo" + placeHolderIndex;
|
|
3867
|
+
let hasLogoLoc = this.ctx.getUniformLocation(this.program, hasLogoUniformName);
|
|
3868
|
+
this.ctx.uniform1i(hasLogoLoc, 0);
|
|
3869
|
+
//unbind the textures that are not required
|
|
3870
|
+
let alphaUnfiromName = "alpha" + placeHolderIndex;
|
|
3871
|
+
let location = this.ctx.getUniformLocation(this.program, alphaUnfiromName);
|
|
3872
|
+
this.ctx.uniform1i(location, 0);
|
|
3873
|
+
}
|
|
3874
|
+
//render the timecode on the canvas (for debug)
|
|
3875
|
+
if (this.options.augmentation.renderTimecode)
|
|
3876
|
+
{{
|
|
3877
|
+
let framesAndField = nextTimeCode.split(":")[3];
|
|
3878
|
+
let field = (framesAndField.indexOf(".") >= 0) ? 2 : 1;
|
|
3879
|
+
let frameLastDigit = parseInt(framesAndField.split(".")[0])%10;
|
|
3880
|
+
let debugImgStr = frameLastDigit + "." + field;//"1.1";
|
|
3881
|
+
//console.log("render " + debugImgStr + " tc = " + nextTimeCode + " scedueled on frame " + debug_lastFrameFromVideoCallback + "(" + debug_tc + ")");
|
|
3882
|
+
let digitTextureNum = this.TexUnitBaseForAlpha() + 5;
|
|
3883
|
+
let digitTex = this.GenerateTexture(
|
|
3884
|
+
this.digitImg[debugImgStr],
|
|
3885
|
+
digitTextureNum,
|
|
3886
|
+
"digitTex");
|
|
3887
|
+
texturesForThisFrame.push(digitTex);
|
|
3888
|
+
let hasDigitUniformName = "hasDigit";
|
|
3889
|
+
let hasDigitLoc = this.ctx.getUniformLocation(this.program, hasDigitUniformName);
|
|
3890
|
+
this.ctx.uniform1i(hasDigitLoc, 1);
|
|
3891
|
+
//set that digit's top-left and size
|
|
3892
|
+
let digitTopLeftUniformName = "u_digitTopLeft";
|
|
3893
|
+
let digitTopLeftLoc = this.ctx.getUniformLocation(this.program, digitTopLeftUniformName);
|
|
3894
|
+
this.ctx.uniform2fv(digitTopLeftLoc, [0, 0]);
|
|
3895
|
+
|
|
3896
|
+
let digitSizeUniformName = "u_digitSize";
|
|
3897
|
+
let digitSizeLoc = this.ctx.getUniformLocation(this.program, digitSizeUniformName);
|
|
3898
|
+
this.ctx.uniform2fv(digitSizeLoc, [200, 200]);
|
|
3899
|
+
|
|
3900
|
+
//let digitTopLeftUniformName2 = "u_digitTopLeft2";
|
|
3901
|
+
//let digitTopLeftLoc2 = this.ctx.getUniformLocation(this.program, digitTopLeftUniformName2);
|
|
3902
|
+
//this.ctx.uniform2fv(digitTopLeftLoc2, [1000, 0]);
|
|
3903
|
+
|
|
3904
|
+
|
|
3905
|
+
}
|
|
3906
|
+
/*{
|
|
3907
|
+
let framesAndField = nextTimeCode.split(":")[3];
|
|
3908
|
+
let field = (framesAndField.indexOf(".") >= 0) ? 2 : 1;
|
|
3909
|
+
let frameLastDigit = parseInt(framesAndField.split(".")[0])%10;
|
|
3910
|
+
let debugImgStr = frameLastDigit + "." + field;//"1.1";
|
|
3911
|
+
//console.log("render " + debugImgStr + " tc = " + nextTimeCode + " scedueled on frame " + debug_lastFrameFromVideoCallback + "(" + debug_tc + ")");
|
|
3912
|
+
let digitTextureNum = this.TexUnitBaseForAlpha() + 5;
|
|
3913
|
+
let digitTex = this.GenerateTexture(
|
|
3914
|
+
this.digitImg[debugImgStr],
|
|
3915
|
+
digitTextureNum,
|
|
3916
|
+
"digitTex");
|
|
3917
|
+
texturesForThisFrame.push(digitTex);
|
|
3918
|
+
let hasDigitUniformName = "hasDigit";
|
|
3919
|
+
let hasDigitLoc = this.ctx.getUniformLocation(this.program, hasDigitUniformName);
|
|
3920
|
+
this.ctx.uniform1i(hasDigitLoc, 1);
|
|
3921
|
+
//set that digit's top-left and size
|
|
3922
|
+
let digitTopLeftUniformName = "u_digitTopLeft2";
|
|
3923
|
+
let digitTopLeftLoc2 = this.ctx.getUniformLocation(this.program, digitTopLeftUniformName);
|
|
3924
|
+
this.ctx.uniform2fv(digitTopLeftLoc2, [1000, 0]);
|
|
3925
|
+
let digitSizeUniformName = "u_digitSize";
|
|
3926
|
+
let digitSizeLoc = this.ctx.getUniformLocation(this.program, digitSizeUniformName);
|
|
3927
|
+
this.ctx.uniform2fv(digitSizeLoc, [200, 200]);
|
|
3928
|
+
}*/
|
|
3929
|
+
/*
|
|
3930
|
+
let adForLogoUniformName = "adForLogo" + placeHolderIndex;
|
|
3931
|
+
let adForLogoUniform = this.ctx.getUniformLocation(this.program, adForLogoUniformName);
|
|
3932
|
+
let isBlur = false;
|
|
3933
|
+
if (holder.name.indexOf("blur") >= 0)
|
|
3934
|
+
{
|
|
3935
|
+
this.ctx.uniform1i(adForLogoUniform, 99);
|
|
3936
|
+
isBlur = true;
|
|
3937
|
+
}
|
|
3938
|
+
else
|
|
3939
|
+
{
|
|
3940
|
+
this.ctx.uniform1i(adForLogoUniform, this.TexUnitForPlaceHolder(holder));
|
|
3941
|
+
}
|
|
3942
|
+
*/
|
|
3943
|
+
}
|
|
3944
|
+
else //no digit
|
|
3945
|
+
{
|
|
3946
|
+
let hasDigitUniformName = "hasDigit";
|
|
3947
|
+
let hasDigitLoc = this.ctx.getUniformLocation(this.program, hasDigitUniformName);
|
|
3948
|
+
this.ctx.uniform1i(hasDigitLoc, 0);
|
|
3949
|
+
}
|
|
3950
|
+
|
|
3951
|
+
//render the frame
|
|
3952
|
+
this.RenderFrame();
|
|
3953
|
+
|
|
3954
|
+
//cleanup:
|
|
3955
|
+
//delete the texture created for this frame
|
|
3956
|
+
for (const texture of texturesForThisFrame) {
|
|
3957
|
+
this.ctx.deleteTexture(texture);
|
|
3958
|
+
}
|
|
3959
|
+
|
|
3960
|
+
}
|
|
3961
|
+
getCanvas() {
|
|
3962
|
+
return this.canvas;
|
|
3963
|
+
}
|
|
3964
|
+
}
|
|
3965
|
+
BB.Ads = (composer, options) => {
|
|
3966
|
+
let selectedAdsList = [];
|
|
3967
|
+
const socket = new BB.Socket((type, message) => {
|
|
3968
|
+
switch (type) {
|
|
3969
|
+
case "roommembers":
|
|
3970
|
+
console.log("roommembers message: ", message);
|
|
3971
|
+
break;
|
|
3972
|
+
case "join":
|
|
3973
|
+
const name = document.getElementById("name");
|
|
3974
|
+
if(name){
|
|
3975
|
+
name.textContent = socket.name;
|
|
3976
|
+
}
|
|
3977
|
+
break;
|
|
3978
|
+
case "messageroom":
|
|
3979
|
+
if (message.player === socket.name) {
|
|
3980
|
+
if (message.url) {
|
|
3981
|
+
const name = "#external";
|
|
3982
|
+
composer.AddAdvertisement(name, message.url, () => {
|
|
3983
|
+
composer.SelectAd(name);
|
|
3984
|
+
});
|
|
3985
|
+
} else {
|
|
3986
|
+
selectedAdsList = message.ads;
|
|
3987
|
+
if (selectedAdsList.length) {
|
|
3988
|
+
composer.SelectAd(selectedAdsList[0]);
|
|
3989
|
+
}
|
|
3990
|
+
}
|
|
3991
|
+
}
|
|
3992
|
+
break;
|
|
3993
|
+
case "ads":
|
|
3994
|
+
selectedAdsList = message;
|
|
3995
|
+
if (selectedAdsList.length) {
|
|
3996
|
+
composer.SelectAd(selectedAdsList[0]);
|
|
3997
|
+
}
|
|
3998
|
+
break;
|
|
3999
|
+
}
|
|
4000
|
+
});
|
|
4001
|
+
|
|
4002
|
+
var script = document.createElement('script');
|
|
4003
|
+
script.src = 'https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.3.0/socket.io.js';
|
|
4004
|
+
document.getElementsByTagName('head')[0].appendChild(script);
|
|
4005
|
+
script.onload = function() {
|
|
4006
|
+
socket.Connect(options.augmentation.channelName);
|
|
4007
|
+
}
|
|
4008
|
+
|
|
4009
|
+
//load the ads for this projects:
|
|
4010
|
+
if (options.augmentation.logoURL) {
|
|
4011
|
+
composer.AddAdvertisement(`#ad`, options.augmentation.logoURL);
|
|
4012
|
+
}
|
|
4013
|
+
else //get logo according to the channel's S3 bucket
|
|
4014
|
+
{
|
|
4015
|
+
fetch("https://www.virtualott.com/ads/" + options.augmentation.channelName).then(async (res) => {
|
|
4016
|
+
const list = await res.json();
|
|
4017
|
+
list.forEach(photo => {
|
|
4018
|
+
composer.AddAdvertisement(`#${photo.name}`, photo.url);
|
|
4019
|
+
});
|
|
4020
|
+
});
|
|
4021
|
+
}
|
|
4022
|
+
|
|
4023
|
+
};
|
|
4024
|
+
BB.MetaDataFetcher = (options, composer) => {
|
|
4025
|
+
let tcTranslator = BB.Utils.TimeCodesFramesTranslator;
|
|
4026
|
+
let sourceFPS = BB.Utils.getSourceFPS(options.frameRateMode);
|
|
4027
|
+
let folderName;
|
|
4028
|
+
let lastFrames;
|
|
4029
|
+
let metadataWorker = null;
|
|
4030
|
+
let overlayWorker = null;
|
|
4031
|
+
console.log("Project name: " + options.gameId);
|
|
4032
|
+
const setMetaDataFolderName = (_folderName) => {
|
|
4033
|
+
folderName = _folderName;
|
|
4034
|
+
console.log("Folder name for " + options.gameId + ": " + folderName);
|
|
4035
|
+
if (lastFrames) {
|
|
4036
|
+
loadMetaData(lastFrames);
|
|
4037
|
+
}
|
|
4038
|
+
};
|
|
4039
|
+
async function getMetaDataFolderName() {
|
|
4040
|
+
const res = await fetch(options.allGamesURL);//get all the games
|
|
4041
|
+
const list = await res.json();//extract the games' list
|
|
4042
|
+
//if (typeof list[options.gameId] != "undefined") {//see if we found the current project
|
|
4043
|
+
// setMetaDataFolderName(list[options.gameId].folder);//set the project's folder
|
|
4044
|
+
//} else {
|
|
4045
|
+
// setTimeout(async ()=>{
|
|
4046
|
+
// await getMetaDataFolderName();//Didn't find it ? Don't give up, try again after 2 secs
|
|
4047
|
+
// },2000);
|
|
4048
|
+
//}
|
|
4049
|
+
setMetaDataFolderName(options.gameId);//set the project's folder
|
|
4050
|
+
}
|
|
4051
|
+
getMetaDataFolderName();
|
|
4052
|
+
const setLastFrame = (framesNum) => {
|
|
4053
|
+
lastFrames = framesNum;
|
|
4054
|
+
if (folderName) {
|
|
4055
|
+
loadMetaData(lastFrames);
|
|
4056
|
+
}
|
|
4057
|
+
}
|
|
4058
|
+
const loadMetaData = (lastFrames) => {
|
|
4059
|
+
//console.BBlog("loadMetaData");
|
|
4060
|
+
if (metadataWorker == null) {
|
|
4061
|
+
console.BBlog("We have no MetaDataDownloaderThread yet. Creating it");
|
|
4062
|
+
let blob = new Blob([META_WORKER], { type: 'application/javascript' });
|
|
4063
|
+
metadataWorker = new Worker(URL.createObjectURL(blob));
|
|
4064
|
+
// Handle errors from the worker
|
|
4065
|
+
metadataWorker.onerror = function(error) {
|
|
4066
|
+
console.error('Error from worker:', error);
|
|
4067
|
+
};
|
|
4068
|
+
|
|
4069
|
+
// Optionally handle the onerror event using addEventListener
|
|
4070
|
+
metadataWorker.addEventListener('error', function(error) {
|
|
4071
|
+
console.error('Error from worker (via addEventListener):', error.message);
|
|
4072
|
+
});
|
|
4073
|
+
if (metadataWorker) console.BBlog("MetaDataDownloaderThread created");
|
|
4074
|
+
else console.BBlog("Error in creating MetaDataDownloaderThread");
|
|
4075
|
+
metadataWorker.addEventListener("message", (msg) => {
|
|
4076
|
+
if (msg.data.time) {
|
|
4077
|
+
//Debug("prof",msg.data.time);
|
|
4078
|
+
} else {
|
|
4079
|
+
//console.BBlog("loaded MetaData");
|
|
4080
|
+
composer.OnMetaData(msg.data);//send to composer
|
|
4081
|
+
}
|
|
4082
|
+
});
|
|
4083
|
+
//get the last timecode with 2-sec round-down
|
|
4084
|
+
let timeCode = tcTranslator.TimeCodeArray2TimeCodeJson(tcTranslator.Frames2Timecode(lastFrames,sourceFPS,true));
|
|
4085
|
+
metadataWorker.postMessage({cloudFrontURL:options.cloudFrontURL, folderName:folderName, tc: tcTranslator.printTimecode(timeCode, false)});
|
|
4086
|
+
}
|
|
4087
|
+
else //the metadata worker is already in action, let it know the current playing data so the max-timecode is updated accordingly
|
|
4088
|
+
{
|
|
4089
|
+
let timeCode = tcTranslator.TimeCodeArray2TimeCodeJson(tcTranslator.Frames2Timecode(lastFrames,sourceFPS,true));
|
|
4090
|
+
metadataWorker.postMessage({tc: tcTranslator.printTimecode(timeCode, false),enabled:options.enabled});
|
|
4091
|
+
}
|
|
4092
|
+
}
|
|
4093
|
+
|
|
4094
|
+
return {
|
|
4095
|
+
setLastFrame:setLastFrame
|
|
4096
|
+
};
|
|
4097
|
+
}
|
|
4098
|
+
BB.OTTPlayer = class {
|
|
4099
|
+
constructor(video, srcFrameRate, optionsFromMK){
|
|
4100
|
+
//first, generate the inital options:
|
|
4101
|
+
this.video = video;
|
|
4102
|
+
this.srcFrameRate = srcFrameRate;
|
|
4103
|
+
console.log("optionsFromMK: ", optionsFromMK);
|
|
4104
|
+
this.options = Object.assign({
|
|
4105
|
+
enabled:true,
|
|
4106
|
+
stopAugment:false, //setted to true when there is exception or smt went wrong
|
|
4107
|
+
gameId:"Unknown",
|
|
4108
|
+
medialook:false,
|
|
4109
|
+
allGamesURL:"https://www.virtualott.com/games/",
|
|
4110
|
+
cloudFrontURL:"https://db8igqxlbvayn.cloudfront.net/prod",
|
|
4111
|
+
mediaIdJsonURL: "https://games-prod.virtualott.com/map",
|
|
4112
|
+
offsetJsonURL: "https://games-prod.virtualott.com/offset",
|
|
4113
|
+
frameRateMode:BB.FrameRateMode.Src59_MediaTime60,
|
|
4114
|
+
renderingMode:BB.RenderingMode.Augment, //MUST, other will not work in this test
|
|
4115
|
+
SynchMethod:BB.SynchMethod.None,
|
|
4116
|
+
debug:false,
|
|
4117
|
+
augmentation: {
|
|
4118
|
+
renderTimecode:true,
|
|
4119
|
+
offset:0
|
|
4120
|
+
},
|
|
4121
|
+
syncPoint: {
|
|
4122
|
+
frame:0,
|
|
4123
|
+
playerTime:0,
|
|
4124
|
+
mediaTimeMS:0
|
|
4125
|
+
}
|
|
4126
|
+
}, optionsFromMK || {});
|
|
4127
|
+
console.log("options after merge with MK's options: ", this.options);
|
|
4128
|
+
this.options.augmentation.channelName = "nba";
|
|
4129
|
+
console.BBlog = function(message) {
|
|
4130
|
+
console.log("OTTPlayer: " + message);
|
|
4131
|
+
};
|
|
4132
|
+
console.BBerror = function(message) {
|
|
4133
|
+
console.error("OTTPlayer: " + message);
|
|
4134
|
+
};
|
|
4135
|
+
//this.options.debug = true; //alal//
|
|
4136
|
+
function compareVersions(version1, version2) {
|
|
4137
|
+
|
|
4138
|
+
const v1 = version1.split('.').map(Number);
|
|
4139
|
+
const v2 = version2.split('.').map(Number);
|
|
4140
|
+
|
|
4141
|
+
for (let i = 0; i < Math.max(v1.length, v2.length); i++) {
|
|
4142
|
+
const num1 = v1[i] || 0;
|
|
4143
|
+
const num2 = v2[i] || 0;
|
|
4144
|
+
|
|
4145
|
+
if (num1 < num2) {
|
|
4146
|
+
return -1;
|
|
4147
|
+
} else if (num1 > num2) {
|
|
4148
|
+
return 1;
|
|
4149
|
+
}
|
|
4150
|
+
}
|
|
4151
|
+
|
|
4152
|
+
return 0;
|
|
4153
|
+
}
|
|
4154
|
+
this.getGlobalConfig();
|
|
4155
|
+
|
|
4156
|
+
|
|
4157
|
+
}
|
|
4158
|
+
//after we merged the options with the url-param global config
|
|
4159
|
+
init() {
|
|
4160
|
+
//override with global options:
|
|
4161
|
+
if (typeof globalConfig === 'undefined')
|
|
4162
|
+
this.options = Object.assign(this.options, {});
|
|
4163
|
+
else
|
|
4164
|
+
this.options = Object.assign(this.options, globalConfig);
|
|
4165
|
+
this.options.augmentation.offset = 0;
|
|
4166
|
+
console.log("options after merge with the global options: ", this.options);
|
|
4167
|
+
//this.options.debug = true; //alal
|
|
4168
|
+
//if the debug option is off - make the debug-div disappear
|
|
4169
|
+
if (!this.options.debug) {
|
|
4170
|
+
let elementsToHide = ["debugText","forplus","forDoNotRender","debugText2","toggleBtn","numberInput","plus05CorrectionCheckbox","doNotRenderAugmentationCheckbox", "plus05CorrectionLabel", "doNotRenderAugmentationLabel", "br1","br2"];
|
|
4171
|
+
for (let i = 0; i < elementsToHide.length; i++) {
|
|
4172
|
+
const elemToHide = document.getElementById(elementsToHide[i]);
|
|
4173
|
+
if (elemToHide) {
|
|
4174
|
+
elemToHide.style.display = "none";
|
|
4175
|
+
}
|
|
4176
|
+
}
|
|
4177
|
+
}
|
|
4178
|
+
|
|
4179
|
+
let platformDetector = BB.Utils.Platform;
|
|
4180
|
+
let tcTranslator = BB.Utils.TimeCodesFramesTranslator;
|
|
4181
|
+
if (this.srcFrameRate) this.SetFps(this.srcFrameRate);
|
|
4182
|
+
this.fetchAndFindGameIdAndOffsetFromMediaId(this.options.gameId);//this will fetch the right game id and the right
|
|
4183
|
+
|
|
4184
|
+
}
|
|
4185
|
+
weHaveTheGameID() {
|
|
4186
|
+
//we need to have this.option.offset updated at this stage so the augmentor will get the right offset
|
|
4187
|
+
let line = {};
|
|
4188
|
+
this.frameDrawer = BB.Augmenter(line, this.video, this.options);
|
|
4189
|
+
if (this.options.renderingMode == BB.RenderingMode.Augment) {
|
|
4190
|
+
//we need the game id to connect to the right bucket
|
|
4191
|
+
this.frameDrawer.initializeComponents();
|
|
4192
|
+
}
|
|
4193
|
+
}
|
|
4194
|
+
async getGlobalConfig() {
|
|
4195
|
+
//make sure we have global config
|
|
4196
|
+
const URLParams = new URL(document.location);
|
|
4197
|
+
const CONFIG_NAME = URLParams.searchParams.get("config") || "";
|
|
4198
|
+
if (CONFIG_NAME !== "")
|
|
4199
|
+
{
|
|
4200
|
+
try {
|
|
4201
|
+
const response = await fetch(CONFIG_NAME);
|
|
4202
|
+
if (!response.ok) {
|
|
4203
|
+
throw new Error('Network response was not ok');
|
|
4204
|
+
}
|
|
4205
|
+
const configJson = await response.json();
|
|
4206
|
+
console.log("Got the following config: ", configJson);
|
|
4207
|
+
if (typeof globalConfig== 'undefined') window.globalConfig = {};
|
|
4208
|
+
globalConfig = Object.assign({}, globalConfig, configJson);
|
|
4209
|
+
|
|
4210
|
+
|
|
4211
|
+
} catch (error) {
|
|
4212
|
+
console.error('Error fetching JSON:', error);
|
|
4213
|
+
}
|
|
4214
|
+
}
|
|
4215
|
+
this.init();
|
|
4216
|
+
}
|
|
4217
|
+
|
|
4218
|
+
// Function to fetch the JSON and find the value for mediaID
|
|
4219
|
+
async fetchAndFindGameIdAndOffsetFromMediaId(mediaID) {
|
|
4220
|
+
try {
|
|
4221
|
+
const response = await fetch(this.options.mediaIdJsonURL+ "/" + mediaID);
|
|
4222
|
+
if (!response.ok) {
|
|
4223
|
+
throw new Error(`HTTP error! Status: ${response.status}`);
|
|
4224
|
+
}
|
|
4225
|
+
|
|
4226
|
+
const jsonData = await response.json();
|
|
4227
|
+
|
|
4228
|
+
if (jsonData.hasOwnProperty("GAME_ID")) {
|
|
4229
|
+
const value = jsonData["GAME_ID"];
|
|
4230
|
+
console.BBlog(`GameId for mediaid= ${mediaID}: ${value}`);
|
|
4231
|
+
this.options.gameId = value;
|
|
4232
|
+
console.log("fetchAndFindGameIdAndOffsetFromMediaId: I Know what is the game id ! Its " + this.options.gameId + ", so I'm launching the meta-data fetcher web-worker");
|
|
4233
|
+
this.weHaveTheGameID();
|
|
4234
|
+
|
|
4235
|
+
//metaDataFetcher = BB.MetaDataFetcher(options,composer);
|
|
4236
|
+
this.fetchOffsetForGameId(this.options.gameId);
|
|
4237
|
+
|
|
4238
|
+
} else {
|
|
4239
|
+
console.BBerror(`MediaID ${mediaID} not found in JSON.`);
|
|
4240
|
+
this.options.stopAugment = true;
|
|
4241
|
+
}
|
|
4242
|
+
} catch (error) {
|
|
4243
|
+
console.BBerror('Error fetching and processing GameId JSON:', error);
|
|
4244
|
+
//this.options.stopAugment = true;
|
|
4245
|
+
}
|
|
4246
|
+
}
|
|
4247
|
+
|
|
4248
|
+
// Function to fetch the JSON and find the value for mediaID
|
|
4249
|
+
async fetchOffsetForGameId(gameId) {
|
|
4250
|
+
try {
|
|
4251
|
+
const response = await fetch(this.options.offsetJsonURL + "/" + this.options.gameId);
|
|
4252
|
+
|
|
4253
|
+
if (!response.ok) {
|
|
4254
|
+
console.BBlog(`HTTP error! Status: ${response.status}`);
|
|
4255
|
+
setTimeout(() => {
|
|
4256
|
+
this.fetchOffsetForGameId(gameId);
|
|
4257
|
+
}, 5000);
|
|
4258
|
+
}
|
|
4259
|
+
|
|
4260
|
+
const jsonData = await response.json();
|
|
4261
|
+
|
|
4262
|
+
if ((jsonData.hasOwnProperty("offset")) && (jsonData.hasOwnProperty("found"))) {
|
|
4263
|
+
const value = jsonData["offset"];
|
|
4264
|
+
const found =jsonData["found"];
|
|
4265
|
+
|
|
4266
|
+
if (found == true)
|
|
4267
|
+
{
|
|
4268
|
+
console.BBlog(`Offest for gameid= ${gameId}: ${value}`);
|
|
4269
|
+
this.options.augmentation.offset = value;
|
|
4270
|
+
//call get-offset again later on
|
|
4271
|
+
setTimeout(() => {
|
|
4272
|
+
this.fetchOffsetForGameId(gameId);
|
|
4273
|
+
}, 60000);
|
|
4274
|
+
}
|
|
4275
|
+
} else {
|
|
4276
|
+
console.BBlog(`offset for ${mediaID} not found in JSON.`);
|
|
4277
|
+
setTimeout(() => {
|
|
4278
|
+
this.fetchOffsetForGameId(gameId);
|
|
4279
|
+
}, 5000);
|
|
4280
|
+
}
|
|
4281
|
+
} catch (error) {
|
|
4282
|
+
console.BBerror('Error fetching and processing JSON:', error);
|
|
4283
|
+
setTimeout(() => {
|
|
4284
|
+
this.fetchOffsetForGameId(gameId);
|
|
4285
|
+
}, 5000);
|
|
4286
|
+
|
|
4287
|
+
}
|
|
4288
|
+
}
|
|
4289
|
+
|
|
4290
|
+
Toggle (flag) {
|
|
4291
|
+
this.options.enabled = flag;
|
|
4292
|
+
}
|
|
4293
|
+
GetCanvas(){
|
|
4294
|
+
return this.frameDrawer.getCanvas();
|
|
4295
|
+
}
|
|
4296
|
+
SetFps(fps){
|
|
4297
|
+
let platformDetector = BB.Utils.Platform;
|
|
4298
|
+
switch(fps)
|
|
4299
|
+
{
|
|
4300
|
+
case 60:
|
|
4301
|
+
this.options.frameRateMode = BB.FrameRateMode.Src60_MediaTime60;
|
|
4302
|
+
break;
|
|
4303
|
+
case 60000/1001:
|
|
4304
|
+
case 59.94:
|
|
4305
|
+
if (platformDetector.isWindows() || platformDetector.isAndroid() || platformDetector.isChromeMac()) {
|
|
4306
|
+
this.options.frameRateMode = BB.FrameRateMode.Src59_MediaTime59;
|
|
4307
|
+
}
|
|
4308
|
+
else { //we are playing on MAC Safary 59.94 source HLS(only) in 59.99 mediaTimeFPS //only for HLS
|
|
4309
|
+
//options.frameRateMode = BB.FrameRateMode.Src59_MediaTime60;
|
|
4310
|
+
this.options.frameRateMode = BB.FrameRateMode.Src59_MediaTime59;
|
|
4311
|
+
}
|
|
4312
|
+
break;
|
|
4313
|
+
case 30000/1001:
|
|
4314
|
+
case 29.97:
|
|
4315
|
+
this.options.frameRateMode = BB.FrameRateMode.Src29_MediaTime29;
|
|
4316
|
+
break;
|
|
4317
|
+
default:
|
|
4318
|
+
console.error("Unsupported src frame-rate: " + fps + "fps");
|
|
4319
|
+
break;
|
|
4320
|
+
}
|
|
4321
|
+
}
|
|
4322
|
+
SetSyncPoint(syncPoint)
|
|
4323
|
+
{
|
|
4324
|
+
this.options.syncPoint = syncPoint;
|
|
4325
|
+
|
|
4326
|
+
}
|
|
4327
|
+
onNewSegment(eventObj)
|
|
4328
|
+
{
|
|
4329
|
+
this.frameDrawer.onNewSegmentInterface(eventObj);
|
|
4330
|
+
}
|
|
4331
|
+
onNewMetadata(eventObj)
|
|
4332
|
+
{
|
|
4333
|
+
this.frameDrawer.onNewMetadataInterface(eventObj);
|
|
4334
|
+
}
|
|
4335
|
+
}
|