vyrplay 1.0.0 → 1.0.1

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.
@@ -1,446 +1,20 @@
1
- // src/engine/MediaEngine.js
2
- var HlsLib = null;
3
- async function ensureHls() {
4
- if (HlsLib) return HlsLib;
5
- if (typeof window !== "undefined" && window.Hls) {
6
- HlsLib = window.Hls;
7
- return HlsLib;
8
- }
9
- try {
10
- const mod = await import("hls.js");
11
- HlsLib = mod.default || mod;
12
- return HlsLib;
13
- } catch (_) {
14
- return new Promise((resolve, reject) => {
15
- if (typeof document === "undefined") return reject(new Error("No Hls"));
16
- const s = document.createElement("script");
17
- s.src = "https://cdn.jsdelivr.net/npm/hls.js@1.5.7";
18
- s.onload = () => {
19
- HlsLib = window.Hls;
20
- resolve(HlsLib);
21
- };
22
- s.onerror = () => reject(new Error("Failed to load hls.js"));
23
- document.head.appendChild(s);
24
- });
25
- }
26
- }
27
- var MediaEngine = class {
28
- constructor(video, opts = {}) {
29
- this.video = video;
30
- this.hls = null;
31
- this.sources = [];
32
- this.currentIndex = 0;
33
- this.currentQuality = null;
34
- this.isHls = false;
35
- this._destroyed = false;
36
- this.onError = opts.onError || (() => {
37
- });
38
- this.onReady = opts.onReady || (() => {
39
- });
40
- this.onQualityChange = opts.onQualityChange || (() => {
41
- });
42
- }
43
- /**
44
- * Normalize options into internal sources list.
45
- * Accepts:
46
- * sources: [{ type, url, label, height }]
47
- * src + type
48
- * single string url
49
- */
50
- setSources(input) {
51
- let list = [];
52
- if (Array.isArray(input) && input.length) {
53
- list = input.map((s) => this._norm(s));
54
- } else if (input && typeof input === "object" && input.url) {
55
- list = [this._norm(input)];
56
- } else if (typeof input === "string") {
57
- list = [this._norm({ url: input })];
58
- }
59
- list.sort((a, b) => (b.height || 0) - (a.height || 0));
60
- const seen = /* @__PURE__ */ new Set();
61
- this.sources = list.filter((s) => {
62
- if (!s.url || seen.has(s.label)) return false;
63
- seen.add(s.label);
64
- return true;
65
- });
66
- this.currentIndex = 0;
67
- this.currentQuality = this.sources[0]?.label || null;
68
- return this.sources;
69
- }
70
- _norm(s) {
71
- if (typeof s === "string") s = { url: s };
72
- const url = String(s.url || "");
73
- let type = (s.type || "").toLowerCase();
74
- if (!type) {
75
- if (/\.m3u8(\?|$)/i.test(url) || /application\/vnd\.apple\.mpegurl/i.test(s.mime || "")) {
76
- type = "hls";
77
- } else {
78
- type = "mp4";
79
- }
80
- }
81
- const height = s.height || this._guessHeight(s.label || s.quality || "") || 0;
82
- const label = s.label || (height ? height + "p" : s.quality || "Auto");
83
- return { type, url, label, height, name: s.name };
84
- }
85
- _guessHeight(str) {
86
- const m = String(str).match(/(\d{3,4})\s*p/i);
87
- if (m) return parseInt(m[1], 10);
88
- const map = { fullhd: 1080, full: 1080, hd: 720, sd: 480, low: 360, mobile: 144, ultra: 2160, quad: 1440 };
89
- return map[String(str).toLowerCase()] || 0;
90
- }
91
- getQualities() {
92
- return this.sources.map((s) => ({ label: s.label, height: s.height, url: s.url, type: s.type }));
93
- }
94
- async load(startTime = 0, autoplay = true) {
95
- if (this._destroyed || !this.sources.length) {
96
- this.onError({ message: "Tidak ada sumber video" });
97
- return false;
98
- }
99
- return this._tryIndex(0, startTime, autoplay);
100
- }
101
- async _tryIndex(idx, startTime, autoplay) {
102
- if (this._destroyed) return false;
103
- if (idx >= this.sources.length) {
104
- this.onError({ message: "Semua sumber gagal dimuat" });
105
- return false;
106
- }
107
- this.currentIndex = idx;
108
- const src = this.sources[idx];
109
- this.currentQuality = src.label;
110
- this.onQualityChange(src.label);
111
- this._destroyHls();
112
- try {
113
- this.video.pause();
114
- this.video.removeAttribute("src");
115
- this.video.load();
116
- } catch (_) {
117
- }
118
- if (src.type === "hls") {
119
- return this._loadHls(src.url, startTime, autoplay, idx);
120
- }
121
- return this._loadDirect(src.url, startTime, autoplay, idx);
122
- }
123
- _cleanUrl(url) {
124
- try {
125
- const u = new URL(url, typeof location !== "undefined" ? location.href : void 0);
126
- u.hash = "";
127
- return u.toString();
128
- } catch (_) {
129
- return String(url || "").split("#")[0];
130
- }
131
- }
132
- _loadDirect(url, startTime, autoplay, idx) {
133
- return new Promise((resolve) => {
134
- let settled = false;
135
- const finish = (ok) => {
136
- if (settled) return;
137
- settled = true;
138
- this.video.removeEventListener("loadedmetadata", onOk);
139
- this.video.removeEventListener("canplay", onOk);
140
- this.video.removeEventListener("error", onErr);
141
- if (!ok) {
142
- this._tryIndex(idx + 1, startTime, autoplay).then(resolve);
143
- return;
144
- }
145
- try {
146
- if (startTime > 0.5 && isFinite(startTime)) {
147
- this.video.currentTime = startTime;
148
- }
149
- } catch (_) {
150
- }
151
- if (autoplay) this.video.play().catch(() => {
152
- });
153
- this.onReady({ quality: this.currentQuality, type: "mp4" });
154
- resolve(true);
155
- };
156
- const onOk = () => finish(true);
157
- const onErr = () => finish(false);
158
- this.video.src = this._cleanUrl(url);
159
- try {
160
- this.video.load();
161
- } catch (_) {
162
- }
163
- this.video.addEventListener("loadedmetadata", onOk, { once: true });
164
- this.video.addEventListener("canplay", onOk, { once: true });
165
- this.video.addEventListener("error", onErr, { once: true });
166
- setTimeout(() => {
167
- if (!settled) {
168
- const ok = this.video.readyState >= 2 || this.video.duration > 0 && isFinite(this.video.duration);
169
- finish(!!ok);
170
- }
171
- }, 18e3);
172
- });
173
- }
174
- async _loadHls(url, startTime, autoplay, idx) {
175
- const clean = this._cleanUrl(url);
176
- try {
177
- if (this.video.canPlayType("application/vnd.apple.mpegurl")) {
178
- return this._loadDirect(clean, startTime, autoplay, idx);
179
- }
180
- const Hls = await ensureHls();
181
- if (!Hls || !Hls.isSupported()) {
182
- return this._tryIndex(idx + 1, startTime, autoplay);
183
- }
184
- this.isHls = true;
185
- this.hls = new Hls({
186
- enableWorker: true,
187
- lowLatencyMode: false,
188
- backBufferLength: 30,
189
- maxBufferLength: 45,
190
- maxMaxBufferLength: 90,
191
- maxBufferHole: 1,
192
- nudgeMaxRetry: 5,
193
- fragLoadingTimeOut: 25e3,
194
- manifestLoadingTimeOut: 2e4,
195
- levelLoadingTimeOut: 2e4,
196
- startPosition: startTime > 0 ? startTime : -1,
197
- xhrSetup: function(xhr) {
198
- try {
199
- xhr.withCredentials = false;
200
- } catch (_) {
201
- }
202
- }
203
- });
204
- this.hls.loadSource(clean);
205
- this.hls.attachMedia(this.video);
206
- return new Promise((resolve) => {
207
- let settled = false;
208
- const finish = (ok, reason) => {
209
- if (settled) return;
210
- settled = true;
211
- if (!ok) {
212
- this._destroyHls();
213
- this._tryIndex(idx + 1, startTime, autoplay).then(resolve);
214
- return;
215
- }
216
- if (startTime > 0.5) {
217
- try {
218
- this.video.currentTime = startTime;
219
- } catch (_) {
220
- }
221
- }
222
- if (autoplay) {
223
- const p = this.video.play();
224
- if (p && p.catch) p.catch(() => {
225
- });
226
- }
227
- this.onReady({ quality: this.currentQuality, type: "hls", reason });
228
- resolve(true);
229
- };
230
- this.hls.on(Hls.Events.MANIFEST_PARSED, (_, data) => {
231
- try {
232
- const levels = this.hls.levels || [];
233
- if (levels.length > 1) {
234
- const mapped = levels.map((lv, i) => ({
235
- label: lv.height ? lv.height + "p" : "L" + i,
236
- height: lv.height || 0,
237
- url: clean,
238
- type: "hls",
239
- level: i
240
- })).sort((a, b) => (b.height || 0) - (a.height || 0));
241
- this._hlsLevels = mapped;
242
- this.sources = mapped.length ? mapped : this.sources;
243
- this.currentQuality = mapped[0]?.label || this.currentQuality;
244
- this.onQualityChange(this.currentQuality);
245
- } else if (levels.length === 1) {
246
- const lv = levels[0];
247
- const label = lv.height ? lv.height + "p" : this.currentQuality || "Auto";
248
- this.currentQuality = label;
249
- this.onQualityChange(label);
250
- }
251
- } catch (_2) {
252
- }
253
- });
254
- this.hls.on(Hls.Events.FRAG_BUFFERED, () => finish(true, "frag"));
255
- this.video.addEventListener("canplay", () => finish(true, "canplay"), { once: true });
256
- this.video.addEventListener("loadeddata", () => {
257
- if (this.video.readyState >= 2) finish(true, "loadeddata");
258
- }, { once: true });
259
- this.hls.on(Hls.Events.ERROR, (_, data) => {
260
- if (!data) return;
261
- if (data.fatal) {
262
- const type = data.type;
263
- try {
264
- if (type === Hls.ErrorTypes.NETWORK_ERROR) {
265
- this.hls.startLoad();
266
- return;
267
- }
268
- if (type === Hls.ErrorTypes.MEDIA_ERROR) {
269
- this.hls.recoverMediaError();
270
- return;
271
- }
272
- } catch (_2) {
273
- }
274
- finish(false, "fatal");
275
- }
276
- });
277
- setTimeout(() => {
278
- if (!settled) {
279
- const ok = this.video.readyState >= 2 || this.video.duration > 0 && isFinite(this.video.duration);
280
- finish(!!ok, "timeout");
281
- }
282
- }, 25e3);
283
- });
284
- } catch (err) {
285
- return this._tryIndex(idx + 1, startTime, autoplay);
286
- }
287
- }
288
- /** Switch quality by label — keep current time */
289
- async switchQuality(label) {
290
- if (!label || label === this.currentQuality) return;
291
- const q = this.sources.find((s) => s.label === label);
292
- if (!q) return;
293
- const time = this.video.currentTime || 0;
294
- const wasPlaying = !this.video.paused;
295
- const prev = this.currentQuality;
296
- this.currentQuality = label;
297
- this.onQualityChange(label);
298
- if (this.isHls && this.hls && this.hls.levels && this.hls.levels.length) {
299
- const wantH = parseInt(String(label).replace(/[^\d]/g, ""), 10) || 0;
300
- let lvl = -1;
301
- for (let i = 0; i < this.hls.levels.length; i++) {
302
- if ((this.hls.levels[i].height || 0) === wantH) {
303
- lvl = i;
304
- break;
305
- }
306
- }
307
- if (lvl < 0 && wantH) {
308
- let best = -1, bestDiff = 1e9;
309
- for (let i = 0; i < this.hls.levels.length; i++) {
310
- const d = Math.abs((this.hls.levels[i].height || 0) - wantH);
311
- if (d < bestDiff) {
312
- bestDiff = d;
313
- best = i;
314
- }
315
- }
316
- lvl = best;
317
- }
318
- if (lvl >= 0) {
319
- this.hls.currentLevel = lvl;
320
- this.hls.loadLevel = lvl;
321
- return;
322
- }
323
- const fromSrc = this.sources.find((s) => s.label === label && s.level != null);
324
- if (fromSrc && fromSrc.level >= 0) {
325
- this.hls.currentLevel = fromSrc.level;
326
- this.hls.loadLevel = fromSrc.level;
327
- return;
328
- }
329
- }
330
- if (q.type === "hls") {
331
- await this._loadHls(q.url, time, wasPlaying, this.currentIndex);
332
- } else {
333
- await this._loadDirect(q.url, time, wasPlaying, this.sources.indexOf(q));
334
- }
335
- }
336
- _destroyHls() {
337
- if (this.hls) {
338
- try {
339
- this.hls.destroy();
340
- } catch (_) {
341
- }
342
- this.hls = null;
343
- }
344
- this.isHls = false;
345
- }
346
- destroy() {
347
- this._destroyed = true;
348
- this._destroyHls();
349
- try {
350
- this.video.pause();
351
- this.video.removeAttribute("src");
352
- this.video.load();
353
- } catch (_) {
354
- }
355
- }
356
- };
357
-
358
- // src/utils/events.js
359
- var Emitter = class {
360
- constructor() {
361
- this._map = /* @__PURE__ */ new Map();
362
- }
363
- on(event, fn) {
364
- if (!this._map.has(event)) this._map.set(event, /* @__PURE__ */ new Set());
365
- this._map.get(event).add(fn);
366
- return () => this.off(event, fn);
367
- }
368
- off(event, fn) {
369
- const set = this._map.get(event);
370
- if (set) set.delete(fn);
371
- }
372
- emit(event, payload) {
373
- const set = this._map.get(event);
374
- if (!set) return;
375
- set.forEach((fn) => {
376
- try {
377
- fn(payload);
378
- } catch (e) {
379
- console.error("[VyrPlay]", e);
380
- }
381
- });
382
- }
383
- destroy() {
384
- this._map.clear();
385
- }
386
- };
387
-
388
- // src/utils/format.js
389
- function fmtTime(s) {
390
- if (!isFinite(s) || s < 0) return "0:00";
391
- s = Math.floor(s);
392
- const h = Math.floor(s / 3600);
393
- const m = Math.floor(s % 3600 / 60);
394
- const sec = s % 60;
395
- if (h > 0) {
396
- return h + ":" + String(m).padStart(2, "0") + ":" + String(sec).padStart(2, "0");
397
- }
398
- return m + ":" + String(sec).padStart(2, "0");
399
- }
400
- function fmtRate(r) {
401
- return (Number.isInteger(r) ? r : r) + "x";
402
- }
403
- function clamp(n, min, max) {
404
- return Math.max(min, Math.min(max, n));
405
- }
406
-
407
- // src/ui/template.js
408
- var BAT_SVG = `<svg viewBox="0 0 420 220" aria-hidden="true">
1
+ var _=null;async function P(){if(_)return _;if(typeof window<"u"&&window.Hls)return _=window.Hls,_;try{let h=await import("hls.js");return _=h.default||h,_}catch{return new Promise((t,s)=>{if(typeof document>"u")return s(new Error("No Hls"));let e=document.createElement("script");e.src="https://cdn.jsdelivr.net/npm/hls.js@1.5.7",e.onload=()=>{_=window.Hls,t(_)},e.onerror=()=>s(new Error("Failed to load hls.js")),document.head.appendChild(e)})}}var m=class{constructor(t,s={}){this.video=t,this.hls=null,this.sources=[],this.currentIndex=0,this.currentQuality=null,this.isHls=!1,this._destroyed=!1,this.onError=s.onError||(()=>{}),this.onReady=s.onReady||(()=>{}),this.onQualityChange=s.onQualityChange||(()=>{})}setSources(t){let s=[];Array.isArray(t)&&t.length?s=t.map(i=>this._norm(i)):t&&typeof t=="object"&&t.url?s=[this._norm(t)]:typeof t=="string"&&(s=[this._norm({url:t})]),s.sort((i,r)=>(r.height||0)-(i.height||0));let e=new Set;return this.sources=s.filter(i=>!i.url||e.has(i.label)?!1:(e.add(i.label),!0)),this.currentIndex=0,this.currentQuality=this.sources[0]?.label||null,this.sources}_norm(t){typeof t=="string"&&(t={url:t});let s=String(t.url||""),e=(t.type||"").toLowerCase();e||(/\.m3u8(\?|$)/i.test(s)||/application\/vnd\.apple\.mpegurl/i.test(t.mime||"")?e="hls":e="mp4");let i=t.height||this._guessHeight(t.label||t.quality||"")||0,r=t.label||(i?i+"p":t.quality||"Auto");return{type:e,url:s,label:r,height:i,name:t.name}}_guessHeight(t){let s=String(t).match(/(\d{3,4})\s*p/i);return s?parseInt(s[1],10):{fullhd:1080,full:1080,hd:720,sd:480,low:360,mobile:144,ultra:2160,quad:1440}[String(t).toLowerCase()]||0}getQualities(){return this.sources.map(t=>({label:t.label,height:t.height,url:t.url,type:t.type}))}async load(t=0,s=!0){return this._destroyed||!this.sources.length?(this.onError({message:"Tidak ada sumber video"}),!1):this._tryIndex(0,t,s)}async _tryIndex(t,s,e){if(this._destroyed)return!1;if(t>=this.sources.length)return this.onError({message:"Semua sumber gagal dimuat"}),!1;this.currentIndex=t;let i=this.sources[t];this.currentQuality=i.label,this.onQualityChange(i.label),this._destroyHls();try{this.video.pause(),this.video.removeAttribute("src"),this.video.load()}catch{}return i.type==="hls"?this._loadHls(i.url,s,e,t):this._loadDirect(i.url,s,e,t)}_cleanUrl(t){try{let s=new URL(t,typeof location<"u"?location.href:void 0);return s.hash="",s.toString()}catch{return String(t||"").split("#")[0]}}_loadDirect(t,s,e,i){return new Promise(r=>{let a=!1,l=v=>{if(!a){if(a=!0,this.video.removeEventListener("loadedmetadata",n),this.video.removeEventListener("canplay",n),this.video.removeEventListener("error",c),!v){this._tryIndex(i+1,s,e).then(r);return}try{s>.5&&isFinite(s)&&(this.video.currentTime=s)}catch{}e&&this.video.play().catch(()=>{}),this.onReady({quality:this.currentQuality,type:"mp4"}),r(!0)}},n=()=>l(!0),c=()=>l(!1);this.video.src=this._cleanUrl(t);try{this.video.load()}catch{}this.video.addEventListener("loadedmetadata",n,{once:!0}),this.video.addEventListener("canplay",n,{once:!0}),this.video.addEventListener("error",c,{once:!0}),setTimeout(()=>{if(!a){let v=this.video.readyState>=2||this.video.duration>0&&isFinite(this.video.duration);l(!!v)}},18e3)})}async _loadHls(t,s,e,i){let r=this._cleanUrl(t);try{if(this.video.canPlayType("application/vnd.apple.mpegurl"))return this._loadDirect(r,s,e,i);let a=await P();return!a||!a.isSupported()?this._tryIndex(i+1,s,e):(this.isHls=!0,this.hls=new a({enableWorker:!0,lowLatencyMode:!1,backBufferLength:30,maxBufferLength:45,maxMaxBufferLength:90,maxBufferHole:1,nudgeMaxRetry:5,fragLoadingTimeOut:25e3,manifestLoadingTimeOut:2e4,levelLoadingTimeOut:2e4,startPosition:s>0?s:-1,xhrSetup:function(l){try{l.withCredentials=!1}catch{}}}),this.hls.loadSource(r),this.hls.attachMedia(this.video),new Promise(l=>{let n=!1,c=(v,y)=>{if(!n){if(n=!0,!v){this._destroyHls(),this._tryIndex(i+1,s,e).then(l);return}if(s>.5)try{this.video.currentTime=s}catch{}if(e){let u=this.video.play();u&&u.catch&&u.catch(()=>{})}this.onReady({quality:this.currentQuality,type:"hls",reason:y}),l(!0)}};this.hls.on(a.Events.MANIFEST_PARSED,(v,y)=>{try{let u=this.hls.levels||[];if(u.length>1){let f=u.map((g,S)=>({label:g.height?g.height+"p":"L"+S,height:g.height||0,url:r,type:"hls",level:S})).sort((g,S)=>(S.height||0)-(g.height||0));this._hlsLevels=f,this.sources=f.length?f:this.sources,this.currentQuality=f[0]?.label||this.currentQuality,this.onQualityChange(this.currentQuality)}else if(u.length===1){let f=u[0],g=f.height?f.height+"p":this.currentQuality||"Auto";this.currentQuality=g,this.onQualityChange(g)}}catch{}}),this.hls.on(a.Events.FRAG_BUFFERED,()=>c(!0,"frag")),this.video.addEventListener("canplay",()=>c(!0,"canplay"),{once:!0}),this.video.addEventListener("loadeddata",()=>{this.video.readyState>=2&&c(!0,"loadeddata")},{once:!0}),this.hls.on(a.Events.ERROR,(v,y)=>{if(y&&y.fatal){let u=y.type;try{if(u===a.ErrorTypes.NETWORK_ERROR){this.hls.startLoad();return}if(u===a.ErrorTypes.MEDIA_ERROR){this.hls.recoverMediaError();return}}catch{}c(!1,"fatal")}}),setTimeout(()=>{if(!n){let v=this.video.readyState>=2||this.video.duration>0&&isFinite(this.video.duration);c(!!v,"timeout")}},25e3)}))}catch{return this._tryIndex(i+1,s,e)}}async switchQuality(t){if(!t||t===this.currentQuality)return;let s=this.sources.find(a=>a.label===t);if(!s)return;let e=this.video.currentTime||0,i=!this.video.paused,r=this.currentQuality;if(this.currentQuality=t,this.onQualityChange(t),this.isHls&&this.hls&&this.hls.levels&&this.hls.levels.length){let a=parseInt(String(t).replace(/[^\d]/g,""),10)||0,l=-1;for(let c=0;c<this.hls.levels.length;c++)if((this.hls.levels[c].height||0)===a){l=c;break}if(l<0&&a){let c=-1,v=1e9;for(let y=0;y<this.hls.levels.length;y++){let u=Math.abs((this.hls.levels[y].height||0)-a);u<v&&(v=u,c=y)}l=c}if(l>=0){this.hls.currentLevel=l,this.hls.loadLevel=l;return}let n=this.sources.find(c=>c.label===t&&c.level!=null);if(n&&n.level>=0){this.hls.currentLevel=n.level,this.hls.loadLevel=n.level;return}}s.type==="hls"?await this._loadHls(s.url,e,i,this.currentIndex):await this._loadDirect(s.url,e,i,this.sources.indexOf(s))}_destroyHls(){if(this.hls){try{this.hls.destroy()}catch{}this.hls=null}this.isHls=!1}destroy(){this._destroyed=!0,this._destroyHls();try{this.video.pause(),this.video.removeAttribute("src"),this.video.load()}catch{}}};var L=class{constructor(){this._map=new Map}on(t,s){return this._map.has(t)||this._map.set(t,new Set),this._map.get(t).add(s),()=>this.off(t,s)}off(t,s){let e=this._map.get(t);e&&e.delete(s)}emit(t,s){let e=this._map.get(t);e&&e.forEach(i=>{try{i(s)}catch(r){console.error("[VyrPlay]",r)}})}destroy(){this._map.clear()}};function k(h){if(!isFinite(h)||h<0)return"0:00";h=Math.floor(h);let t=Math.floor(h/3600),s=Math.floor(h%3600/60),e=h%60;return t>0?t+":"+String(s).padStart(2,"0")+":"+String(e).padStart(2,"0"):s+":"+String(e).padStart(2,"0")}function b(h){return Number.isInteger(h),h+"x"}function w(h,t,s){return Math.max(t,Math.min(s,h))}var T=`<svg viewBox="0 0 420 220" aria-hidden="true">
409
2
  <path class="vyr-bat-base" d="M210 22 L188 65 Q176 82 186 96 Q142 85 110 120 Q85 92 60 80 Q85 45 28 20 Q65 22 115 55 Q155 85 185 120 Q195 175 210 205 Q225 175 235 120 Q265 85 305 55 Q355 22 392 20 Q335 45 360 80 Q335 92 310 120 Q278 85 234 96 Q244 82 232 65 L210 22"/>
410
3
  <path class="vyr-bat-line" d="M210 22 L188 65 Q176 82 186 96 Q142 85 110 120 Q85 92 60 80 Q85 45 28 20 Q65 22 115 55 Q155 85 185 120 Q195 175 210 205 Q225 175 235 120 Q265 85 305 55 Q355 22 392 20 Q335 45 360 80 Q335 92 310 120 Q278 85 234 96 Q244 82 232 65 L210 22"/>
411
- </svg>`;
412
- var ICONS = {
413
- back: `<svg viewBox="0 0 24 24"><path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></svg>`,
414
- pip: `<svg viewBox="0 0 24 24"><path d="M19 7h-8v6h8V7zm2-4H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 14H3V5h18v12z"/></svg>`,
415
- settings: `<svg viewBox="0 0 24 24"><path d="M19.14 12.94c.04-.31.06-.63.06-.94s-.02-.63-.06-.94l2.03-1.58a.49.49 0 0 0 .12-.61l-1.92-3.32a.49.49 0 0 0-.59-.22l-2.39.96a7.2 7.2 0 0 0-1.62-.94l-.36-2.54A.48.48 0 0 0 14 2h-4a.48.48 0 0 0-.48.41l-.36 2.54c-.59.24-1.13.56-1.62.94l-2.39-.96a.49.49 0 0 0-.59.22L2.65 8.87a.48.48 0 0 0 .12.61l2.03 1.58c-.04.31-.06.63-.06.94s.02.63.06.94l-2.03 1.58a.49.49 0 0 0-.12.61l1.92 3.32c.13.23.4.32.64.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h4c.24 0 .44-.17.48-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.23.1.51 0 .64-.22l1.92-3.32a.49.49 0 0 0-.12-.61l-2.03-1.58zM12 15.5A3.5 3.5 0 1 1 12 8.5a3.5 3.5 0 0 1 0 7z"/></svg>`,
416
- lockOpen: `<svg class="vyr-ic-lock-open" viewBox="0 0 24 24"><path d="M12 17a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm6-7h-1V7A5 5 0 0 0 7 7h1.5a3.5 3.5 0 0 1 7 0v3H6a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8a2 2 0 0 0-2-2z"/></svg>`,
417
- lockClosed: `<svg class="vyr-ic-lock-closed" viewBox="0 0 24 24" style="display:none"><path d="M12 17a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm6-7h-1V7A5 5 0 0 0 7 7v3H6a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8a2 2 0 0 0-2-2zM8.5 7a3.5 3.5 0 0 1 7 0v3h-7V7z"/></svg>`,
418
- play: `<svg class="vyr-ic-play" viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>`,
419
- pause: `<svg class="vyr-ic-pause" viewBox="0 0 24 24" style="display:none"><path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/></svg>`,
420
- rew: `<svg viewBox="0 0 24 24"><path d="M11.99 5V1l-5 5 5 5V7c3.31 0 6 2.69 6 6s-2.69 6-6 6-6-2.69-6-6h-2c0 4.42 3.58 8 8 8s8-3.58 8-8-3.58-8-8-8z"/></svg>`,
421
- ff: `<svg viewBox="0 0 24 24"><path d="M12 5V1l5 5-5 5V7c-3.31 0-6 2.69-6 6s2.69 6 6 6 6-2.69 6-6h2c0 4.42-3.58 8-8 8s-8-3.58-8-8 3.58-8 8-8z"/></svg>`,
422
- fs: `<svg viewBox="0 0 24 24"><path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"/></svg>`,
423
- volOn: `<svg class="vyr-ic-vol-on" viewBox="0 0 24 24"><path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/></svg>`,
424
- cc: `<svg viewBox="0 0 24 24"><path d="M19 4H5a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2zm-8 7H9.5v-.5h-2v3h2V13H11v1a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1v1zm7 0h-1.5v-.5h-2v3h2V13H18v1a1 1 0 0 1-1 1h-3a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1v1z"/></svg>`,
425
- volOff: `<svg class="vyr-ic-vol-off" viewBox="0 0 24 24" style="display:none"><path d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z"/></svg>`
426
- };
427
- function buildTemplate(opts = {}) {
428
- const title = opts.title || "VyrPlay";
429
- const f = opts.features || {};
430
- return `
4
+ </svg>`,p={back:'<svg viewBox="0 0 24 24"><path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></svg>',pip:'<svg viewBox="0 0 24 24"><path d="M19 7h-8v6h8V7zm2-4H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 14H3V5h18v12z"/></svg>',settings:'<svg viewBox="0 0 24 24"><path d="M19.14 12.94c.04-.31.06-.63.06-.94s-.02-.63-.06-.94l2.03-1.58a.49.49 0 0 0 .12-.61l-1.92-3.32a.49.49 0 0 0-.59-.22l-2.39.96a7.2 7.2 0 0 0-1.62-.94l-.36-2.54A.48.48 0 0 0 14 2h-4a.48.48 0 0 0-.48.41l-.36 2.54c-.59.24-1.13.56-1.62.94l-2.39-.96a.49.49 0 0 0-.59.22L2.65 8.87a.48.48 0 0 0 .12.61l2.03 1.58c-.04.31-.06.63-.06.94s.02.63.06.94l-2.03 1.58a.49.49 0 0 0-.12.61l1.92 3.32c.13.23.4.32.64.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h4c.24 0 .44-.17.48-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.23.1.51 0 .64-.22l1.92-3.32a.49.49 0 0 0-.12-.61l-2.03-1.58zM12 15.5A3.5 3.5 0 1 1 12 8.5a3.5 3.5 0 0 1 0 7z"/></svg>',lockOpen:'<svg class="vyr-ic-lock-open" viewBox="0 0 24 24"><path d="M12 17a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm6-7h-1V7A5 5 0 0 0 7 7h1.5a3.5 3.5 0 0 1 7 0v3H6a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8a2 2 0 0 0-2-2z"/></svg>',lockClosed:'<svg class="vyr-ic-lock-closed" viewBox="0 0 24 24" style="display:none"><path d="M12 17a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm6-7h-1V7A5 5 0 0 0 7 7v3H6a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8a2 2 0 0 0-2-2zM8.5 7a3.5 3.5 0 0 1 7 0v3h-7V7z"/></svg>',play:'<svg class="vyr-ic-play" viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>',pause:'<svg class="vyr-ic-pause" viewBox="0 0 24 24" style="display:none"><path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/></svg>',rew:'<svg viewBox="0 0 24 24"><path d="M11.99 5V1l-5 5 5 5V7c3.31 0 6 2.69 6 6s-2.69 6-6 6-6-2.69-6-6h-2c0 4.42 3.58 8 8 8s8-3.58 8-8-3.58-8-8-8z"/></svg>',ff:'<svg viewBox="0 0 24 24"><path d="M12 5V1l5 5-5 5V7c-3.31 0-6 2.69-6 6s2.69 6 6 6 6-2.69 6-6h2c0 4.42-3.58 8-8 8s-8-3.58-8-8 3.58-8 8-8z"/></svg>',fs:'<svg viewBox="0 0 24 24"><path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"/></svg>',volOn:'<svg class="vyr-ic-vol-on" viewBox="0 0 24 24"><path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/></svg>',cc:'<svg viewBox="0 0 24 24"><path d="M19 4H5a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2zm-8 7H9.5v-.5h-2v3h2V13H11v1a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1v1zm7 0h-1.5v-.5h-2v3h2V13H18v1a1 1 0 0 1-1 1h-3a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1v1z"/></svg>',volOff:'<svg class="vyr-ic-vol-off" viewBox="0 0 24 24" style="display:none"><path d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z"/></svg>'};function E(h={}){let t=h.title||"VyrPlay",s=h.features||{};return`
431
5
  <div class="vyr-loading">
432
- <div class="vyr-bat-wrap">${BAT_SVG}</div>
6
+ <div class="vyr-bat-wrap">${T}</div>
433
7
  <div class="label">LOADING</div>
434
8
  </div>
435
9
  <div class="vyr-buffer" aria-hidden="true">
436
- <div class="vyr-bat-wrap">${BAT_SVG}</div>
10
+ <div class="vyr-bat-wrap">${T}</div>
437
11
  </div>
438
12
  <div class="vyr-poster hide"></div>
439
13
  <video playsinline webkit-playsinline preload="metadata" controlslist="nodownload noplaybackrate"></video>
440
14
  <div class="vyr-sub-layer pos-bottom" aria-live="polite"><div class="vyr-sub-text"></div></div>
441
15
  <div class="vyr-pip-overlay" aria-hidden="true">
442
16
  <div class="vyr-pip-box">
443
- ${ICONS.pip}
17
+ ${p.pip}
444
18
  <div style="font-size:16px;font-weight:800;margin:12px 0 6px">Picture-in-Picture aktif</div>
445
19
  <div style="font-size:13px;opacity:.65;margin-bottom:8px">Video diputar di jendela mengambang</div>
446
20
  <button type="button" class="vyr-btn-pip-back">Kembali ke player</button>
@@ -456,28 +30,28 @@ function buildTemplate(opts = {}) {
456
30
  <div class="vyr-ui">
457
31
  <div class="vyr-top">
458
32
  <div class="vyr-top-left">
459
- ${f.back !== false ? `<button class="vyr-icon-btn vyr-btn-back" title="Kembali">${ICONS.back}</button>` : ""}
460
- <div class="vyr-title">${escapeHtml(title)}</div>
33
+ ${s.back!==!1?`<button class="vyr-icon-btn vyr-btn-back" title="Kembali">${p.back}</button>`:""}
34
+ <div class="vyr-title">${M(t)}</div>
461
35
  </div>
462
36
  <div class="vyr-top-right">
463
- ${f.pip !== false ? `<button class="vyr-icon-btn vyr-btn-pip" title="PiP">${ICONS.pip}</button>` : ""}
464
- ${f.settings !== false ? `<button class="vyr-icon-btn vyr-btn-settings" title="Pengaturan">${ICONS.settings}</button>` : ""}
465
- ${f.lockUi !== false ? `<button class="vyr-icon-btn vyr-btn-lock" title="Kunci UI">${ICONS.lockOpen}${ICONS.lockClosed}</button>` : ""}
37
+ ${s.pip!==!1?`<button class="vyr-icon-btn vyr-btn-pip" title="PiP">${p.pip}</button>`:""}
38
+ ${s.settings!==!1?`<button class="vyr-icon-btn vyr-btn-settings" title="Pengaturan">${p.settings}</button>`:""}
39
+ ${s.lockUi!==!1?`<button class="vyr-icon-btn vyr-btn-lock" title="Kunci UI">${p.lockOpen}${p.lockClosed}</button>`:""}
466
40
  </div>
467
41
  </div>
468
42
 
469
43
  <div class="vyr-center">
470
- ${f.seekButtons !== false ? `
44
+ ${s.seekButtons!==!1?`
471
45
  <button class="vyr-c-btn vyr-btn-rew" aria-label="Mundur">
472
- ${ICONS.rew}<span class="seek-badge vyr-badge-rew">10</span>
473
- </button>` : ""}
46
+ ${p.rew}<span class="seek-badge vyr-badge-rew">10</span>
47
+ </button>`:""}
474
48
  <button class="vyr-c-btn play vyr-btn-pp" aria-label="Play/Pause">
475
- <div class="ring"></div>${ICONS.play}${ICONS.pause}
49
+ <div class="ring"></div>${p.play}${p.pause}
476
50
  </button>
477
- ${f.seekButtons !== false ? `
51
+ ${s.seekButtons!==!1?`
478
52
  <button class="vyr-c-btn vyr-btn-ff" aria-label="Maju">
479
- ${ICONS.ff}<span class="seek-badge vyr-badge-ff">10</span>
480
- </button>` : ""}
53
+ ${p.ff}<span class="seek-badge vyr-badge-ff">10</span>
54
+ </button>`:""}
481
55
  </div>
482
56
 
483
57
  <div class="vyr-bottom">
@@ -491,14 +65,14 @@ function buildTemplate(opts = {}) {
491
65
  <div class="vyr-meta">
492
66
  <div class="vyr-time">0:00 / 0:00</div>
493
67
  <div class="vyr-meta-right">
494
- ${f.volume !== false ? `
68
+ ${s.volume!==!1?`
495
69
  <div class="vyr-vol-wrap">
496
- <button type="button" class="vyr-vol-btn vyr-btn-vol" title="Mute">${ICONS.volOn}${ICONS.volOff}</button>
70
+ <button type="button" class="vyr-vol-btn vyr-btn-vol" title="Mute">${p.volOn}${p.volOff}</button>
497
71
  <input type="range" class="vyr-vol-slider" min="0" max="100" step="1" value="100" aria-label="Volume" />
498
- </div>` : ""}
499
- ${f.subtitles !== false ? `<button class="vyr-chip vyr-btn-cc" title="Subtitle">CC</button>` : ""}
500
- ${f.quality !== false ? `<button class="vyr-chip vyr-btn-quality" title="Kualitas">HD</button>` : ""}
501
- <button class="vyr-chip vyr-btn-fs" title="Fullscreen">${ICONS.fs}</button>
72
+ </div>`:""}
73
+ ${s.subtitles!==!1?'<button class="vyr-chip vyr-btn-cc" title="Subtitle">CC</button>':""}
74
+ ${s.quality!==!1?'<button class="vyr-chip vyr-btn-quality" title="Kualitas">HD</button>':""}
75
+ <button class="vyr-chip vyr-btn-fs" title="Fullscreen">${p.fs}</button>
502
76
  </div>
503
77
  </div>
504
78
  </div>
@@ -613,1310 +187,12 @@ function buildTemplate(opts = {}) {
613
187
  </div>
614
188
  </div>
615
189
  </div>
616
- `;
617
- }
618
- function escapeHtml(s) {
619
- return String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
620
- }
621
-
622
- // src/VyrPlay.js
623
- var SEEK_STEPS = [5, 10, 15, 30, 60];
624
- var SPEED_OPTS = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2];
625
- var HOLD_SPEED_OPTS = [1.5, 1.75, 2, 2.5, 3];
626
- var LS = {
627
- seek: "vyrplay_seek_step",
628
- rate: "vyrplay_rate",
629
- holdOn: "vyrplay_hold_speed",
630
- holdRate: "vyrplay_hold_rate",
631
- vol: "vyrplay_volume",
632
- muted: "vyrplay_muted",
633
- subY: "vyrplay_sub_y",
634
- subOn: "vyrplay_sub_on",
635
- subColor: "vyrplay_sub_color",
636
- subSize: "vyrplay_sub_size",
637
- subBg: "vyrplay_sub_bg",
638
- subLang: "vyrplay_sub_lang"
639
- };
640
- function $(root, sel) {
641
- if (!root) return null;
642
- if (typeof root === "string") return document.querySelector(root);
643
- return root.querySelector(sel);
644
- }
645
- function $$(root, sel) {
646
- if (!root) return [];
647
- return Array.from(root.querySelectorAll(sel));
648
- }
649
- var VyrPlay = class {
650
- /**
651
- * @param {string|HTMLElement} container
652
- * @param {object} options
653
- */
654
- constructor(container, options = {}) {
655
- this.el = typeof container === "string" ? document.querySelector(container) : container;
656
- if (!this.el) throw new Error("[VyrPlay] Container not found");
657
- this.opts = this._mergeOpts(options);
658
- this.emitter = new Emitter();
659
- if (this.opts.on) {
660
- Object.entries(this.opts.on).forEach(([ev, fn]) => {
661
- if (typeof fn === "function") this.emitter.on(ev, fn);
662
- });
663
- }
664
- this.uiVisible = true;
665
- this.uiLocked = false;
666
- this.seeking = false;
667
- this.hideTimer = null;
668
- this.seekStep = this._loadNum(LS.seek, this.opts.settings.seekStep, 1, 300);
669
- this.playRate = this._loadNum(LS.rate, this.opts.settings.playbackRate, 0.25, 3);
670
- this.holdSpeedOn = this._loadBool(LS.holdOn, this.opts.settings.holdSpeedEnabled);
671
- this.holdRate = this._loadNum(LS.holdRate, this.opts.settings.holdSpeed, 1, 4);
672
- this.volLevel = this._loadNum(LS.vol, 1, 0, 1);
673
- this.volMuted = this._loadBool(LS.muted, false);
674
- this.subY = this._loadNum(LS.subY, 12, 0, 80);
675
- this.subOn = this._loadBool(LS.subOn, true);
676
- this.subColor = typeof localStorage !== "undefined" && localStorage.getItem(LS.subColor) || "#ffffff";
677
- this.subSize = this._loadNum(LS.subSize, 20, 14, 42);
678
- this.subBg = this._loadNum(LS.subBg, 55, 0, 100);
679
- this.subLang = typeof localStorage !== "undefined" && localStorage.getItem(LS.subLang) || "";
680
- this._tracks = [];
681
- this._activeTrackIdx = -1;
682
- this.isHoldingSpeed = false;
683
- this.holdTimer = null;
684
- this._destroyed = false;
685
- this._injectStyles();
686
- this._buildDOM();
687
- this._bindRefs();
688
- this.audioEl = null;
689
- this._audioUrl = null;
690
- this._bindUI();
691
- this._applySettingsUI();
692
- this._applySubStyle();
693
- this.engine = new MediaEngine(this.video, {
694
- onReady: (info) => {
695
- this._hideLoading();
696
- this._hideBuffer();
697
- this._updatePlayIcon();
698
- this._updateProgress();
699
- this._renderQualities();
700
- this._showUI();
701
- if (this.opts.tracks && this.opts.tracks.length) {
702
- this._setupTracks(this.opts.tracks);
703
- }
704
- this.emitter.emit("ready", this.getState());
705
- },
706
- onError: (err) => {
707
- this._showError(err.message || "Gagal memuat video");
708
- this.emitter.emit("error", err);
709
- },
710
- onQualityChange: (label) => {
711
- if (this.btnQuality) this.btnQuality.textContent = label || "HD";
712
- this._renderQualities();
713
- }
714
- });
715
- if (this.opts.sources || this.opts.src) {
716
- this.load(this.opts);
717
- } else {
718
- this._hideLoading();
719
- }
720
- }
721
- _mergeOpts(o) {
722
- return {
723
- title: o.title || "VyrPlay",
724
- poster: o.poster || null,
725
- startTime: o.startTime || 0,
726
- sources: o.sources || null,
727
- src: o.src || null,
728
- type: o.type || null,
729
- audio: o.audio || null,
730
- features: {
731
- quality: true,
732
- pip: true,
733
- lockUi: true,
734
- holdToSpeed: true,
735
- volume: true,
736
- seekButtons: true,
737
- settings: true,
738
- subtitles: true,
739
- back: false,
740
- ...o.features
741
- },
742
- settings: {
743
- seekStep: 10,
744
- playbackRate: 1,
745
- holdSpeed: 2,
746
- holdSpeedEnabled: true,
747
- ...o.settings
748
- },
749
- on: o.on || {},
750
- ...o
751
- };
752
- }
753
- _loadNum(key, def, min, max) {
754
- try {
755
- const v = parseFloat(localStorage.getItem(key));
756
- if (isFinite(v) && v >= min && v <= max) return v;
757
- } catch (_) {
758
- }
759
- return def;
760
- }
761
- _loadBool(key, def) {
762
- try {
763
- const v = localStorage.getItem(key);
764
- if (v === "0" || v === "false") return false;
765
- if (v === "1" || v === "true") return true;
766
- } catch (_) {
767
- }
768
- return def;
769
- }
770
- _save(key, val) {
771
- try {
772
- localStorage.setItem(key, String(val));
773
- } catch (_) {
774
- }
775
- }
776
- _injectStyles() {
777
- if (document.getElementById("vyrplay-styles")) return;
778
- }
779
- _buildDOM() {
780
- try {
781
- const parent = this.el.parentElement;
782
- if (parent) {
783
- const ps = window.getComputedStyle(parent);
784
- if (ps.position === "static") parent.style.position = "relative";
785
- }
786
- } catch (_) {
787
- }
788
- this.el.classList.add("vyr-root");
789
- try {
790
- const cs = window.getComputedStyle(this.el);
791
- if ((!cs.height || cs.height === "0px") && !this.el.style.height) {
792
- this.el.style.width = this.el.style.width || "100%";
793
- this.el.style.aspectRatio = this.el.style.aspectRatio || "16 / 9";
794
- }
795
- } catch (_) {
796
- }
797
- this.el.innerHTML = buildTemplate({
798
- title: this.opts.title,
799
- features: this.opts.features
800
- });
801
- if (this.opts.poster) {
802
- const p = this.el.querySelector(".vyr-poster");
803
- if (p) {
804
- p.style.backgroundImage = `url("${this.opts.poster}")`;
805
- p.classList.remove("hide");
806
- }
807
- }
808
- }
809
- _bindRefs() {
810
- const r = this.el;
811
- this.video = $(r, "video");
812
- this.ui = $(r, ".vyr-ui");
813
- this.loading = $(r, ".vyr-loading");
814
- this.buffer = $(r, ".vyr-buffer");
815
- this.poster = $(r, ".vyr-poster");
816
- this.btnPP = $(r, ".vyr-btn-pp");
817
- this.btnRew = $(r, ".vyr-btn-rew");
818
- this.btnFf = $(r, ".vyr-btn-ff");
819
- this.btnQuality = $(r, ".vyr-btn-quality");
820
- this.btnFs = $(r, ".vyr-btn-fs");
821
- this.btnLock = $(r, ".vyr-btn-lock");
822
- this.btnSettings = $(r, ".vyr-btn-settings");
823
- this.btnPip = $(r, ".vyr-btn-pip");
824
- this.btnBack = $(r, ".vyr-btn-back");
825
- this.progress = $(r, ".vyr-progress");
826
- this.pFill = $(r, ".vyr-progress-fill");
827
- this.pThumb = $(r, ".vyr-progress-thumb");
828
- this.timeLabel = $(r, ".vyr-time");
829
- this.scrubTip = $(r, ".vyr-scrub-tip");
830
- this.titleEl = $(r, ".vyr-title");
831
- this.speedBadge = $(r, ".vyr-speed-badge");
832
- this.volToast = $(r, ".vyr-vol-toast");
833
- this.backdrop = $(r, ".vyr-backdrop");
834
- this.sheetQuality = $(r, ".vyr-sheet-quality");
835
- this.sheetSettings = $(r, ".vyr-sheet-settings");
836
- this.sheetSeek = $(r, ".vyr-sheet-seek");
837
- this.sheetSpeed = $(r, ".vyr-sheet-speed");
838
- this.sheetHold = $(r, ".vyr-sheet-hold");
839
- this.qualityList = $(r, ".vyr-quality-list");
840
- this.seekList = $(r, ".vyr-seek-list");
841
- this.speedList = $(r, ".vyr-speed-list");
842
- this.holdList = $(r, ".vyr-hold-list");
843
- this.pipOverlay = $(r, ".vyr-pip-overlay");
844
- this.subLayer = $(r, ".vyr-sub-layer");
845
- this.subText = $(r, ".vyr-sub-text");
846
- this.btnCc = $(r, ".vyr-btn-cc");
847
- this.sheetSubs = $(r, ".vyr-sheet-subs");
848
- this.sheetSubstyle = $(r, ".vyr-sheet-substyle");
849
- this.subsList = $(r, ".vyr-subs-list");
850
- }
851
- // ─── Public API ───────────────────────────────────────────
852
- /** Load new sources / options */
853
- async load(opts = {}) {
854
- if (this._destroyed) return;
855
- this._showLoading();
856
- if (opts.title && this.titleEl) this.titleEl.textContent = opts.title;
857
- if (opts.poster && this.poster) {
858
- this.poster.style.backgroundImage = `url("${opts.poster}")`;
859
- this.poster.classList.remove("hide");
860
- }
861
- const input = opts.sources || (opts.src ? [{ url: opts.src, type: opts.type }] : this.opts.sources || this.opts.src);
862
- this.engine.setSources(input);
863
- const start = opts.startTime ?? this.opts.startTime ?? 0;
864
- if (opts.tracks) {
865
- this.opts.tracks = opts.tracks;
866
- }
867
- if (opts.audio !== void 0) {
868
- this.opts.audio = opts.audio;
869
- }
870
- await this._setupAudio(this.opts.audio || null);
871
- await this.engine.load(start, true);
872
- if (this.opts.tracks && this.opts.tracks.length) {
873
- await this._setupTracks(this.opts.tracks);
874
- }
875
- }
876
- play() {
877
- const p = this.video.play();
878
- this._audioPlay();
879
- return p;
880
- }
881
- pause() {
882
- this.video.pause();
883
- this._audioPause();
884
- }
885
- toggle() {
886
- if (this.video.paused) this.play().catch(() => {
887
- });
888
- else this.pause();
889
- }
890
- seek(sec) {
891
- this._safeSeek(sec);
892
- }
893
- setQuality(label) {
894
- return this.engine.switchQuality(label);
895
- }
896
- setPlaybackRate(r) {
897
- this.playRate = r;
898
- this._save(LS.rate, r);
899
- if (!this.isHoldingSpeed) {
900
- try {
901
- this.video.playbackRate = r;
902
- } catch (_) {
903
- }
904
- try {
905
- if (this.audioEl) this.audioEl.playbackRate = r;
906
- } catch (_) {
907
- }
908
- }
909
- const el = this.el.querySelector(".vyr-set-speed-val");
910
- if (el) el.textContent = fmtRate(r);
911
- this._renderSpeedList();
912
- }
913
- getState() {
914
- return {
915
- currentTime: this.video?.currentTime || 0,
916
- duration: this.video && isFinite(this.video.duration) ? this.video.duration : 0,
917
- paused: !this.video || this.video.paused,
918
- quality: this.engine?.currentQuality,
919
- playbackRate: this.playRate,
920
- volume: this.volLevel,
921
- muted: this.volMuted,
922
- subtitle: this._activeTrackIdx >= 0 ? this._tracks[this._activeTrackIdx]?.label || this.subLang : null
923
- };
924
- }
925
- on(event, fn) {
926
- return this.emitter.on(event, fn);
927
- }
928
- off(event, fn) {
929
- this.emitter.off(event, fn);
930
- }
931
- /** Audio terpisah (CDN yang video/audio stream beda URL) */
932
- async _setupAudio(url) {
933
- if (this._audioHls) {
934
- try {
935
- this._audioHls.destroy();
936
- } catch (_) {
937
- }
938
- this._audioHls = null;
939
- }
940
- if (this.audioEl) {
941
- try {
942
- this.audioEl.pause();
943
- } catch (_) {
944
- }
945
- try {
946
- this.audioEl.removeAttribute("src");
947
- this.audioEl.load();
948
- } catch (_) {
949
- }
950
- try {
951
- this.audioEl.remove();
952
- } catch (_) {
953
- }
954
- this.audioEl = null;
955
- }
956
- this._audioUrl = url || null;
957
- if (!url) {
958
- try {
959
- this.video.muted = !!this.volMuted;
960
- this.video.volume = this.volMuted ? 0 : this.volLevel;
961
- } catch (_) {
962
- }
963
- this._syncAudioTime = null;
964
- return;
965
- }
966
- const clean = String(url).split("#")[0];
967
- const a = document.createElement("audio");
968
- a.preload = "auto";
969
- a.setAttribute("playsinline", "");
970
- a.style.display = "none";
971
- this.el.appendChild(a);
972
- this.audioEl = a;
973
- try {
974
- this.video.muted = true;
975
- a.volume = this.volMuted ? 0 : this.volLevel;
976
- a.muted = !!this.volMuted;
977
- } catch (_) {
978
- }
979
- const isHls = /\.m3u8(\?|$)/i.test(clean);
980
- if (isHls && a.canPlayType("application/vnd.apple.mpegurl")) {
981
- a.src = clean;
982
- } else if (isHls) {
983
- try {
984
- let Hls = window.Hls;
985
- if (!Hls) {
986
- await new Promise((resolve, reject) => {
987
- const s = document.createElement("script");
988
- s.src = "https://cdn.jsdelivr.net/npm/hls.js@1.5.7";
989
- s.onload = () => resolve();
990
- s.onerror = reject;
991
- document.head.appendChild(s);
992
- });
993
- Hls = window.Hls;
994
- }
995
- if (Hls && Hls.isSupported()) {
996
- this._audioHls = new Hls({ enableWorker: true, maxBufferLength: 30 });
997
- this._audioHls.loadSource(clean);
998
- this._audioHls.attachMedia(a);
999
- } else {
1000
- a.src = clean;
1001
- }
1002
- } catch (err) {
1003
- console.warn("[VyrPlay] audio hls failed", err);
1004
- a.src = clean;
1005
- }
1006
- } else {
1007
- a.src = clean;
1008
- }
1009
- this._syncAudioTime = () => {
1010
- if (!this.audioEl || !this.video) return;
1011
- try {
1012
- const drift = Math.abs((this.audioEl.currentTime || 0) - (this.video.currentTime || 0));
1013
- if (drift > 0.35) this.audioEl.currentTime = this.video.currentTime || 0;
1014
- } catch (_) {
1015
- }
1016
- };
1017
- a.addEventListener("error", () => console.warn("[VyrPlay] audio track failed", url));
1018
- }
1019
- _audioPlay() {
1020
- if (!this.audioEl) return;
1021
- try {
1022
- this.audioEl.currentTime = this.video.currentTime || 0;
1023
- this.audioEl.playbackRate = this.video.playbackRate || 1;
1024
- this.audioEl.play().catch(() => {
1025
- });
1026
- } catch (_) {
1027
- }
1028
- }
1029
- _audioPause() {
1030
- if (!this.audioEl) return;
1031
- try {
1032
- this.audioEl.pause();
1033
- } catch (_) {
1034
- }
1035
- }
1036
- // ─── Subtitles ──────────────────────────────────────────
1037
- /** @param {Array<{src,label,srclang,kind,default}>} tracks */
1038
- setTracks(tracks) {
1039
- this._setupTracks(tracks || []);
1040
- }
1041
- async _setupTracks(tracks) {
1042
- this._tracks = Array.isArray(tracks) ? tracks.map((t) => ({ ...t })) : [];
1043
- (this._vttBlobs || []).forEach((u) => {
1044
- try {
1045
- URL.revokeObjectURL(u);
1046
- } catch (_) {
1047
- }
1048
- });
1049
- this._vttBlobs = [];
1050
- if (this.video) {
1051
- Array.from(this.video.querySelectorAll("track")).forEach((t) => t.remove());
1052
- try {
1053
- for (let i = 0; i < this.video.textTracks.length; i++) {
1054
- this.video.textTracks[i].mode = "disabled";
1055
- }
1056
- } catch (_) {
1057
- }
1058
- }
1059
- this._activeTrackIdx = -1;
1060
- if (this.subText) this.subText.textContent = "";
1061
- if (!this._tracks.length) {
1062
- if (this.btnCc) this.btnCc.style.display = "none";
1063
- this._applySubStyle();
1064
- return;
1065
- }
1066
- if (this.btnCc) this.btnCc.style.display = "";
1067
- for (let i = 0; i < this._tracks.length; i++) {
1068
- const t = this._tracks[i];
1069
- let text = null;
1070
- try {
1071
- const res = await fetch(t.src, { mode: "cors", credentials: "omit" });
1072
- if (res.ok) text = await res.text();
1073
- } catch (err) {
1074
- console.warn("[VyrPlay] VTT fetch CORS failed", t.src, err);
1075
- }
1076
- if (!text) {
1077
- try {
1078
- const res2 = await fetch(t.src);
1079
- if (res2.ok) text = await res2.text();
1080
- } catch (_) {
1081
- }
1082
- }
1083
- if (text && text.trim()) {
1084
- this._tracks[i]._cues = this._parseVtt(text);
1085
- console.log("[VyrPlay] sub track", t.label || t.srclang || i, "cues=", this._tracks[i]._cues.length);
1086
- } else {
1087
- this._tracks[i]._cues = [];
1088
- console.warn("[VyrPlay] VTT empty/failed", t.src);
1089
- }
1090
- }
1091
- let idx = this._tracks.findIndex((t) => t.default);
1092
- if (idx < 0) idx = this._tracks.findIndex((t) => t._cues && t._cues.length);
1093
- if (idx < 0) idx = 0;
1094
- this.subOn = true;
1095
- this._save(LS.subOn, "1");
1096
- this._enableTrack(idx);
1097
- this._renderSubsList();
1098
- this._applySubStyle();
1099
- this._syncManualSubs();
1100
- }
1101
- _enableTrack(idx) {
1102
- if (idx < 0 || idx >= this._tracks.length) {
1103
- this._disableAllTracks();
1104
- return;
1105
- }
1106
- this._activeTrackIdx = idx;
1107
- this.subOn = true;
1108
- this._save(LS.subOn, "1");
1109
- const t = this._tracks[idx];
1110
- if (t) {
1111
- this.subLang = t.srclang || t.label || "";
1112
- this._save(LS.subLang, this.subLang);
1113
- }
1114
- this.btnCc?.classList.add("active");
1115
- this._applySubStyle();
1116
- this._renderSubsList();
1117
- this._syncManualSubs();
1118
- }
1119
- _disableAllTracks() {
1120
- this._activeTrackIdx = -1;
1121
- try {
1122
- for (let i = 0; i < this.video.textTracks.length; i++) {
1123
- this.video.textTracks[i].mode = "disabled";
1124
- }
1125
- } catch (_) {
1126
- }
1127
- if (this.subText) this.subText.textContent = "";
1128
- this.btnCc?.classList.remove("active");
1129
- this._save(LS.subLang, "");
1130
- this.subLang = "";
1131
- this._applySubStyle();
1132
- this._renderSubsList();
1133
- }
1134
- _parseVtt(text) {
1135
- const cues = [];
1136
- if (!text) return cues;
1137
- const blocks = String(text).replace(/\r/g, "").split(/\n\n+/);
1138
- const ts = (s) => {
1139
- const m = String(s || "").trim().replace(",", ".").match(/(?:(\d+):)?(\d{1,2}):(\d{1,2})(?:\.(\d{1,3}))?/);
1140
- if (!m) return 0;
1141
- const h = parseInt(m[1] || "0", 10);
1142
- const mi = parseInt(m[2], 10);
1143
- const sec = parseInt(m[3], 10);
1144
- const ms = parseInt((m[4] || "0").padEnd(3, "0"), 10);
1145
- return h * 3600 + mi * 60 + sec + ms / 1e3;
1146
- };
1147
- for (const block of blocks) {
1148
- const lines = block.split("\n").filter((l) => l.trim() !== "");
1149
- if (!lines.length || /^WEBVTT/i.test(lines[0]) || /^NOTE/.test(lines[0])) continue;
1150
- let i = 0;
1151
- if (lines[0].includes("-->")) i = 0;
1152
- else if (lines[1] && lines[1].includes("-->")) i = 1;
1153
- else continue;
1154
- const parts = lines[i].split("-->");
1155
- if (parts.length < 2) continue;
1156
- const start = ts(parts[0]);
1157
- const endRaw = (parts[1] || "").trim().split(/\s+/)[0];
1158
- const end = ts(endRaw);
1159
- const body = lines.slice(i + 1).join("\n").replace(/<[^>]+>/g, "");
1160
- if (body) cues.push({ start, end, text: body });
1161
- }
1162
- return cues;
1163
- }
1164
- _syncManualSubs() {
1165
- if (!this.subOn || this._activeTrackIdx < 0 || !this.subText) {
1166
- if (this.subText && (!this.subOn || this._activeTrackIdx < 0)) this.subText.textContent = "";
1167
- return;
1168
- }
1169
- const tr = this._tracks[this._activeTrackIdx];
1170
- const cues = tr && tr._cues;
1171
- if (!cues || !cues.length) return;
1172
- const now = this.video ? this.video.currentTime || 0 : 0;
1173
- let text = "";
1174
- for (let i = 0; i < cues.length; i++) {
1175
- const cu = cues[i];
1176
- if (now >= cu.start && now <= cu.end + 0.08) {
1177
- text = text ? text + "\n" + cu.text : cu.text;
1178
- }
1179
- }
1180
- if (this.subText.textContent !== text) this.subText.textContent = text;
1181
- }
1182
- _onCueChange(tt) {
1183
- const tr = this._tracks[this._activeTrackIdx];
1184
- if (tr && tr._cues && tr._cues.length) {
1185
- this._syncManualSubs();
1186
- return;
1187
- }
1188
- if (this._activeTrackIdx < 0) {
1189
- if (this.subText) this.subText.textContent = "";
1190
- return;
1191
- }
1192
- try {
1193
- const active = this.video.textTracks[this._activeTrackIdx];
1194
- if (tt && active && tt !== active) return;
1195
- const cues = active && active.activeCues ? active.activeCues : null;
1196
- if (!cues || !cues.length) {
1197
- if (this.subText) this.subText.textContent = "";
1198
- return;
1199
- }
1200
- const parts = [];
1201
- for (let i = 0; i < cues.length; i++) parts.push(cues[i].text);
1202
- if (this.subText) this.subText.textContent = parts.join("\n");
1203
- } catch (_) {
1204
- if (this.subText) this.subText.textContent = "";
1205
- }
1206
- }
1207
- _applySubStyle() {
1208
- if (!this.subLayer || !this.subText) return;
1209
- this.el.style.setProperty("--vyr-sub-y", String(this.subY));
1210
- this.subLayer.style.display = this.subOn && this._activeTrackIdx >= 0 ? "flex" : "none";
1211
- this.subText.style.color = this.subColor;
1212
- this.subText.style.fontSize = this.subSize + "px";
1213
- this.subText.style.fontFamily = "var(--vyr-font)";
1214
- this.subText.style.background = "rgba(0,0,0," + (this.subBg / 100).toFixed(2) + ")";
1215
- const color = this.el.querySelector(".vyr-sub-color");
1216
- const size = this.el.querySelector(".vyr-sub-size");
1217
- const bg = this.el.querySelector(".vyr-sub-bg");
1218
- const y = this.el.querySelector(".vyr-sub-y");
1219
- const sizeVal = this.el.querySelector(".vyr-sub-size-val");
1220
- const bgVal = this.el.querySelector(".vyr-sub-bg-val");
1221
- const yVal = this.el.querySelector(".vyr-sub-y-val");
1222
- const tog = this.el.querySelector("[data-tog-sub]");
1223
- if (color) color.value = this.subColor;
1224
- if (size) size.value = String(this.subSize);
1225
- if (bg) bg.value = String(this.subBg);
1226
- if (y) y.value = String(this.subY);
1227
- if (sizeVal) sizeVal.textContent = this.subSize + "px";
1228
- if (bgVal) bgVal.textContent = this.subBg + "%";
1229
- if (yVal) yVal.textContent = this.subY + "%";
1230
- if (tog) tog.classList.toggle("on", this.subOn);
1231
- }
1232
- _renderSubsList() {
1233
- if (!this.subsList) return;
1234
- let html = '<div class="vyr-sheet-item' + (this._activeTrackIdx < 0 ? " active" : "") + '" data-sub-idx="-1">Off<span class="check">\u2713</span></div>';
1235
- this._tracks.forEach((t, i) => {
1236
- const label = t.label || t.srclang || "Track " + (i + 1);
1237
- html += '<div class="vyr-sheet-item' + (i === this._activeTrackIdx ? " active" : "") + '" data-sub-idx="' + i + '">' + label + '<span class="check">\u2713</span></div>';
1238
- });
1239
- this.subsList.innerHTML = html;
1240
- }
1241
- destroy() {
1242
- this._destroyed = true;
1243
- clearTimeout(this.hideTimer);
1244
- clearTimeout(this.holdTimer);
1245
- (this._vttBlobs || []).forEach((u) => {
1246
- try {
1247
- URL.revokeObjectURL(u);
1248
- } catch (_) {
1249
- }
1250
- });
1251
- this._vttBlobs = [];
1252
- this.engine?.destroy();
1253
- this.emitter.destroy();
1254
- this.el.innerHTML = "";
1255
- this.el.classList.remove("vyr-root", "is-fs");
1256
- }
1257
- // ─── Internal UI ──────────────────────────────────────────
1258
- _showLoading() {
1259
- this.loading?.classList.remove("hide");
1260
- }
1261
- _hideLoading() {
1262
- this.loading?.classList.add("hide");
1263
- }
1264
- _showBuffer() {
1265
- this.buffer?.classList.add("show");
1266
- this.btnPP?.classList.add("loading");
1267
- }
1268
- _hideBuffer() {
1269
- this.buffer?.classList.remove("show");
1270
- this.btnPP?.classList.remove("loading");
1271
- }
1272
- _showError(msg) {
1273
- if (!this.loading) return;
1274
- this.loading.innerHTML = `
190
+ `}function M(h){return String(h).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}var H=[5,10,15,30,60],C=[.25,.5,.75,1,1.25,1.5,1.75,2],Q=[1.5,1.75,2,2.5,3],d={seek:"vyrplay_seek_step",rate:"vyrplay_rate",holdOn:"vyrplay_hold_speed",holdRate:"vyrplay_hold_rate",vol:"vyrplay_volume",muted:"vyrplay_muted",subY:"vyrplay_sub_y",subOn:"vyrplay_sub_on",subColor:"vyrplay_sub_color",subSize:"vyrplay_sub_size",subBg:"vyrplay_sub_bg",subLang:"vyrplay_sub_lang"};function o(h,t){return h?typeof h=="string"?document.querySelector(h):h.querySelector(t):null}function I(h,t){return h?Array.from(h.querySelectorAll(t)):[]}var x=class{constructor(t,s={}){if(this.el=typeof t=="string"?document.querySelector(t):t,!this.el)throw new Error("[VyrPlay] Container not found");this.opts=this._mergeOpts(s),this.emitter=new L,this.opts.on&&Object.entries(this.opts.on).forEach(([e,i])=>{typeof i=="function"&&this.emitter.on(e,i)}),this.uiVisible=!0,this.uiLocked=!1,this.seeking=!1,this.hideTimer=null,this.seekStep=this._loadNum(d.seek,this.opts.settings.seekStep,1,300),this.playRate=this._loadNum(d.rate,this.opts.settings.playbackRate,.25,3),this.holdSpeedOn=this._loadBool(d.holdOn,this.opts.settings.holdSpeedEnabled),this.holdRate=this._loadNum(d.holdRate,this.opts.settings.holdSpeed,1,4),this.volLevel=this._loadNum(d.vol,1,0,1),this.volMuted=this._loadBool(d.muted,!1),this.subY=this._loadNum(d.subY,12,0,80),this.subOn=this._loadBool(d.subOn,!0),this.subColor=typeof localStorage<"u"&&localStorage.getItem(d.subColor)||"#ffffff",this.subSize=this._loadNum(d.subSize,20,14,42),this.subBg=this._loadNum(d.subBg,55,0,100),this.subLang=typeof localStorage<"u"&&localStorage.getItem(d.subLang)||"",this._tracks=[],this._activeTrackIdx=-1,this.isHoldingSpeed=!1,this.holdTimer=null,this._destroyed=!1,this._injectStyles(),this._buildDOM(),this._bindRefs(),this.audioEl=null,this._audioUrl=null,this._bindUI(),this._applySettingsUI(),this._applySubStyle(),this.engine=new m(this.video,{onReady:e=>{this._hideLoading(),this._hideBuffer(),this._updatePlayIcon(),this._updateProgress(),this._renderQualities(),this._showUI(),this.opts.tracks&&this.opts.tracks.length&&this._setupTracks(this.opts.tracks),this.emitter.emit("ready",this.getState())},onError:e=>{this._showError(e.message||"Gagal memuat video"),this.emitter.emit("error",e)},onQualityChange:e=>{this.btnQuality&&(this.btnQuality.textContent=e||"HD"),this._renderQualities()}}),this.opts.sources||this.opts.src?this.load(this.opts):this._hideLoading()}_mergeOpts(t){return{title:t.title||"VyrPlay",poster:t.poster||null,startTime:t.startTime||0,sources:t.sources||null,src:t.src||null,type:t.type||null,audio:t.audio||null,features:{quality:!0,pip:!0,lockUi:!0,holdToSpeed:!0,volume:!0,seekButtons:!0,settings:!0,subtitles:!0,back:!1,...t.features},settings:{seekStep:10,playbackRate:1,holdSpeed:2,holdSpeedEnabled:!0,...t.settings},on:t.on||{},...t}}_loadNum(t,s,e,i){try{let r=parseFloat(localStorage.getItem(t));if(isFinite(r)&&r>=e&&r<=i)return r}catch{}return s}_loadBool(t,s){try{let e=localStorage.getItem(t);if(e==="0"||e==="false")return!1;if(e==="1"||e==="true")return!0}catch{}return s}_save(t,s){try{localStorage.setItem(t,String(s))}catch{}}_injectStyles(){document.getElementById("vyrplay-styles")}_buildDOM(){try{let t=this.el.parentElement;t&&window.getComputedStyle(t).position==="static"&&(t.style.position="relative")}catch{}this.el.classList.add("vyr-root");try{let t=window.getComputedStyle(this.el);(!t.height||t.height==="0px")&&!this.el.style.height&&(this.el.style.width=this.el.style.width||"100%",this.el.style.aspectRatio=this.el.style.aspectRatio||"16 / 9")}catch{}if(this.el.innerHTML=E({title:this.opts.title,features:this.opts.features}),this.opts.poster){let t=this.el.querySelector(".vyr-poster");t&&(t.style.backgroundImage=`url("${this.opts.poster}")`,t.classList.remove("hide"))}}_bindRefs(){let t=this.el;this.video=o(t,"video"),this.ui=o(t,".vyr-ui"),this.loading=o(t,".vyr-loading"),this.buffer=o(t,".vyr-buffer"),this.poster=o(t,".vyr-poster"),this.btnPP=o(t,".vyr-btn-pp"),this.btnRew=o(t,".vyr-btn-rew"),this.btnFf=o(t,".vyr-btn-ff"),this.btnQuality=o(t,".vyr-btn-quality"),this.btnFs=o(t,".vyr-btn-fs"),this.btnLock=o(t,".vyr-btn-lock"),this.btnSettings=o(t,".vyr-btn-settings"),this.btnPip=o(t,".vyr-btn-pip"),this.btnBack=o(t,".vyr-btn-back"),this.progress=o(t,".vyr-progress"),this.pFill=o(t,".vyr-progress-fill"),this.pThumb=o(t,".vyr-progress-thumb"),this.timeLabel=o(t,".vyr-time"),this.scrubTip=o(t,".vyr-scrub-tip"),this.titleEl=o(t,".vyr-title"),this.speedBadge=o(t,".vyr-speed-badge"),this.volToast=o(t,".vyr-vol-toast"),this.backdrop=o(t,".vyr-backdrop"),this.sheetQuality=o(t,".vyr-sheet-quality"),this.sheetSettings=o(t,".vyr-sheet-settings"),this.sheetSeek=o(t,".vyr-sheet-seek"),this.sheetSpeed=o(t,".vyr-sheet-speed"),this.sheetHold=o(t,".vyr-sheet-hold"),this.qualityList=o(t,".vyr-quality-list"),this.seekList=o(t,".vyr-seek-list"),this.speedList=o(t,".vyr-speed-list"),this.holdList=o(t,".vyr-hold-list"),this.pipOverlay=o(t,".vyr-pip-overlay"),this.subLayer=o(t,".vyr-sub-layer"),this.subText=o(t,".vyr-sub-text"),this.btnCc=o(t,".vyr-btn-cc"),this.sheetSubs=o(t,".vyr-sheet-subs"),this.sheetSubstyle=o(t,".vyr-sheet-substyle"),this.subsList=o(t,".vyr-subs-list")}async load(t={}){if(this._destroyed)return;this._showLoading(),t.title&&this.titleEl&&(this.titleEl.textContent=t.title),t.poster&&this.poster&&(this.poster.style.backgroundImage=`url("${t.poster}")`,this.poster.classList.remove("hide"));let s=t.sources||(t.src?[{url:t.src,type:t.type}]:this.opts.sources||this.opts.src);this.engine.setSources(s);let e=t.startTime??this.opts.startTime??0;t.tracks&&(this.opts.tracks=t.tracks),t.audio!==void 0&&(this.opts.audio=t.audio),await this._setupAudio(this.opts.audio||null),await this.engine.load(e,!0),this.opts.tracks&&this.opts.tracks.length&&await this._setupTracks(this.opts.tracks)}play(){let t=this.video.play();return this._audioPlay(),t}pause(){this.video.pause(),this._audioPause()}toggle(){this.video.paused?this.play().catch(()=>{}):this.pause()}seek(t){this._safeSeek(t)}setQuality(t){return this.engine.switchQuality(t)}setPlaybackRate(t){if(this.playRate=t,this._save(d.rate,t),!this.isHoldingSpeed){try{this.video.playbackRate=t}catch{}try{this.audioEl&&(this.audioEl.playbackRate=t)}catch{}}let s=this.el.querySelector(".vyr-set-speed-val");s&&(s.textContent=b(t)),this._renderSpeedList()}getState(){return{currentTime:this.video?.currentTime||0,duration:this.video&&isFinite(this.video.duration)?this.video.duration:0,paused:!this.video||this.video.paused,quality:this.engine?.currentQuality,playbackRate:this.playRate,volume:this.volLevel,muted:this.volMuted,subtitle:this._activeTrackIdx>=0?this._tracks[this._activeTrackIdx]?.label||this.subLang:null}}on(t,s){return this.emitter.on(t,s)}off(t,s){this.emitter.off(t,s)}async _setupAudio(t){if(this._audioHls){try{this._audioHls.destroy()}catch{}this._audioHls=null}if(this.audioEl){try{this.audioEl.pause()}catch{}try{this.audioEl.removeAttribute("src"),this.audioEl.load()}catch{}try{this.audioEl.remove()}catch{}this.audioEl=null}if(this._audioUrl=t||null,!t){try{this.video.muted=!!this.volMuted,this.video.volume=this.volMuted?0:this.volLevel}catch{}this._syncAudioTime=null;return}let s=String(t).split("#")[0],e=document.createElement("audio");e.preload="auto",e.setAttribute("playsinline",""),e.style.display="none",this.el.appendChild(e),this.audioEl=e;try{this.video.muted=!0,e.volume=this.volMuted?0:this.volLevel,e.muted=!!this.volMuted}catch{}let i=/\.m3u8(\?|$)/i.test(s);if(i&&e.canPlayType("application/vnd.apple.mpegurl"))e.src=s;else if(i)try{let r=window.Hls;r||(await new Promise((a,l)=>{let n=document.createElement("script");n.src="https://cdn.jsdelivr.net/npm/hls.js@1.5.7",n.onload=()=>a(),n.onerror=l,document.head.appendChild(n)}),r=window.Hls),r&&r.isSupported()?(this._audioHls=new r({enableWorker:!0,maxBufferLength:30}),this._audioHls.loadSource(s),this._audioHls.attachMedia(e)):e.src=s}catch(r){console.warn("[VyrPlay] audio hls failed",r),e.src=s}else e.src=s;this._syncAudioTime=()=>{if(!(!this.audioEl||!this.video))try{Math.abs((this.audioEl.currentTime||0)-(this.video.currentTime||0))>.35&&(this.audioEl.currentTime=this.video.currentTime||0)}catch{}},e.addEventListener("error",()=>console.warn("[VyrPlay] audio track failed",t))}_audioPlay(){if(this.audioEl)try{this.audioEl.currentTime=this.video.currentTime||0,this.audioEl.playbackRate=this.video.playbackRate||1,this.audioEl.play().catch(()=>{})}catch{}}_audioPause(){if(this.audioEl)try{this.audioEl.pause()}catch{}}setTracks(t){this._setupTracks(t||[])}async _setupTracks(t){if(this._tracks=Array.isArray(t)?t.map(e=>({...e})):[],(this._vttBlobs||[]).forEach(e=>{try{URL.revokeObjectURL(e)}catch{}}),this._vttBlobs=[],this.video){Array.from(this.video.querySelectorAll("track")).forEach(e=>e.remove());try{for(let e=0;e<this.video.textTracks.length;e++)this.video.textTracks[e].mode="disabled"}catch{}}if(this._activeTrackIdx=-1,this.subText&&(this.subText.textContent=""),!this._tracks.length){this.btnCc&&(this.btnCc.style.display="none"),this._applySubStyle();return}this.btnCc&&(this.btnCc.style.display="");for(let e=0;e<this._tracks.length;e++){let i=this._tracks[e],r=null;try{let a=await fetch(i.src,{mode:"cors",credentials:"omit"});a.ok&&(r=await a.text())}catch(a){console.warn("[VyrPlay] VTT fetch CORS failed",i.src,a)}if(!r)try{let a=await fetch(i.src);a.ok&&(r=await a.text())}catch{}r&&r.trim()?(this._tracks[e]._cues=this._parseVtt(r),console.log("[VyrPlay] sub track",i.label||i.srclang||e,"cues=",this._tracks[e]._cues.length)):(this._tracks[e]._cues=[],console.warn("[VyrPlay] VTT empty/failed",i.src))}let s=this._tracks.findIndex(e=>e.default);s<0&&(s=this._tracks.findIndex(e=>e._cues&&e._cues.length)),s<0&&(s=0),this.subOn=!0,this._save(d.subOn,"1"),this._enableTrack(s),this._renderSubsList(),this._applySubStyle(),this._syncManualSubs()}_enableTrack(t){if(t<0||t>=this._tracks.length){this._disableAllTracks();return}this._activeTrackIdx=t,this.subOn=!0,this._save(d.subOn,"1");let s=this._tracks[t];s&&(this.subLang=s.srclang||s.label||"",this._save(d.subLang,this.subLang)),this.btnCc?.classList.add("active"),this._applySubStyle(),this._renderSubsList(),this._syncManualSubs()}_disableAllTracks(){this._activeTrackIdx=-1;try{for(let t=0;t<this.video.textTracks.length;t++)this.video.textTracks[t].mode="disabled"}catch{}this.subText&&(this.subText.textContent=""),this.btnCc?.classList.remove("active"),this._save(d.subLang,""),this.subLang="",this._applySubStyle(),this._renderSubsList()}_parseVtt(t){let s=[];if(!t)return s;let e=String(t).replace(/\r/g,"").split(/\n\n+/),i=r=>{let a=String(r||"").trim().replace(",",".").match(/(?:(\d+):)?(\d{1,2}):(\d{1,2})(?:\.(\d{1,3}))?/);if(!a)return 0;let l=parseInt(a[1]||"0",10),n=parseInt(a[2],10),c=parseInt(a[3],10),v=parseInt((a[4]||"0").padEnd(3,"0"),10);return l*3600+n*60+c+v/1e3};for(let r of e){let a=r.split(`
191
+ `).filter(f=>f.trim()!=="");if(!a.length||/^WEBVTT/i.test(a[0])||/^NOTE/.test(a[0]))continue;let l=0;if(a[0].includes("-->"))l=0;else if(a[1]&&a[1].includes("-->"))l=1;else continue;let n=a[l].split("-->");if(n.length<2)continue;let c=i(n[0]),v=(n[1]||"").trim().split(/\s+/)[0],y=i(v),u=a.slice(l+1).join(`
192
+ `).replace(/<[^>]+>/g,"");u&&s.push({start:c,end:y,text:u})}return s}_syncManualSubs(){if(!this.subOn||this._activeTrackIdx<0||!this.subText){this.subText&&(!this.subOn||this._activeTrackIdx<0)&&(this.subText.textContent="");return}let t=this._tracks[this._activeTrackIdx],s=t&&t._cues;if(!s||!s.length)return;let e=this.video&&this.video.currentTime||0,i="";for(let r=0;r<s.length;r++){let a=s[r];e>=a.start&&e<=a.end+.08&&(i=i?i+`
193
+ `+a.text:a.text)}this.subText.textContent!==i&&(this.subText.textContent=i)}_onCueChange(t){let s=this._tracks[this._activeTrackIdx];if(s&&s._cues&&s._cues.length){this._syncManualSubs();return}if(this._activeTrackIdx<0){this.subText&&(this.subText.textContent="");return}try{let e=this.video.textTracks[this._activeTrackIdx];if(t&&e&&t!==e)return;let i=e&&e.activeCues?e.activeCues:null;if(!i||!i.length){this.subText&&(this.subText.textContent="");return}let r=[];for(let a=0;a<i.length;a++)r.push(i[a].text);this.subText&&(this.subText.textContent=r.join(`
194
+ `))}catch{this.subText&&(this.subText.textContent="")}}_applySubStyle(){if(!this.subLayer||!this.subText)return;this.el.style.setProperty("--vyr-sub-y",String(this.subY)),this.subLayer.style.display=this.subOn&&this._activeTrackIdx>=0?"flex":"none",this.subText.style.color=this.subColor,this.subText.style.fontSize=this.subSize+"px",this.subText.style.fontFamily="var(--vyr-font)",this.subText.style.background="rgba(0,0,0,"+(this.subBg/100).toFixed(2)+")";let t=this.el.querySelector(".vyr-sub-color"),s=this.el.querySelector(".vyr-sub-size"),e=this.el.querySelector(".vyr-sub-bg"),i=this.el.querySelector(".vyr-sub-y"),r=this.el.querySelector(".vyr-sub-size-val"),a=this.el.querySelector(".vyr-sub-bg-val"),l=this.el.querySelector(".vyr-sub-y-val"),n=this.el.querySelector("[data-tog-sub]");t&&(t.value=this.subColor),s&&(s.value=String(this.subSize)),e&&(e.value=String(this.subBg)),i&&(i.value=String(this.subY)),r&&(r.textContent=this.subSize+"px"),a&&(a.textContent=this.subBg+"%"),l&&(l.textContent=this.subY+"%"),n&&n.classList.toggle("on",this.subOn)}_renderSubsList(){if(!this.subsList)return;let t='<div class="vyr-sheet-item'+(this._activeTrackIdx<0?" active":"")+'" data-sub-idx="-1">Off<span class="check">\u2713</span></div>';this._tracks.forEach((s,e)=>{let i=s.label||s.srclang||"Track "+(e+1);t+='<div class="vyr-sheet-item'+(e===this._activeTrackIdx?" active":"")+'" data-sub-idx="'+e+'">'+i+'<span class="check">\u2713</span></div>'}),this.subsList.innerHTML=t}destroy(){this._destroyed=!0,clearTimeout(this.hideTimer),clearTimeout(this.holdTimer),(this._vttBlobs||[]).forEach(t=>{try{URL.revokeObjectURL(t)}catch{}}),this._vttBlobs=[],this.engine?.destroy(),this.emitter.destroy(),this.el.innerHTML="",this.el.classList.remove("vyr-root","is-fs")}_showLoading(){this.loading?.classList.remove("hide")}_hideLoading(){this.loading?.classList.add("hide")}_showBuffer(){this.buffer?.classList.add("show"),this.btnPP?.classList.add("loading")}_hideBuffer(){this.buffer?.classList.remove("show"),this.btnPP?.classList.remove("loading")}_showError(t){this.loading&&(this.loading.innerHTML=`
1275
195
  <div class="vyr-error">
1276
196
  <div class="vyr-error-title">Gagal memuat</div>
1277
- <div class="vyr-error-detail">${msg}</div>
1278
- </div>`;
1279
- this.loading.classList.remove("hide");
1280
- }
1281
- _updatePlayIcon() {
1282
- const paused = this.video.paused;
1283
- const play = this.el.querySelector(".vyr-ic-play");
1284
- const pause = this.el.querySelector(".vyr-ic-pause");
1285
- if (play) play.style.display = paused ? "block" : "none";
1286
- if (pause) pause.style.display = paused ? "none" : "block";
1287
- }
1288
- _paintProgress(t, d, showTip) {
1289
- d = d != null && isFinite(d) ? d : this.video?.duration || 0;
1290
- t = isFinite(t) ? Math.max(0, t) : 0;
1291
- if (d > 0) t = Math.min(t, d);
1292
- const pct = d > 0 ? t / d * 100 : 0;
1293
- if (this.pFill) this.pFill.style.width = pct + "%";
1294
- if (this.pThumb) this.pThumb.style.left = pct + "%";
1295
- if (this.timeLabel) this.timeLabel.textContent = fmtTime(t) + " / " + fmtTime(d);
1296
- if (this.scrubTip) {
1297
- this.scrubTip.textContent = fmtTime(t);
1298
- this.scrubTip.style.left = pct + "%";
1299
- this.scrubTip.classList.toggle("show", !!showTip);
1300
- }
1301
- }
1302
- _updateProgress() {
1303
- if (this.seeking) return;
1304
- this._paintProgress(this.video.currentTime || 0, this.video.duration || 0, false);
1305
- }
1306
- _safeSeek(sec) {
1307
- if (!this.video || !isFinite(sec)) return;
1308
- const d = this.video.duration;
1309
- if (isFinite(d) && d > 0) sec = clamp(sec, 0, Math.max(0, d - 0.3));
1310
- else sec = Math.max(0, sec);
1311
- this._paintProgress(sec, d, false);
1312
- const wasPlaying = !this.video.paused;
1313
- this.btnPP?.classList.add("loading");
1314
- let done = false;
1315
- const finish = () => {
1316
- if (done) return;
1317
- done = true;
1318
- this.btnPP?.classList.remove("loading");
1319
- if (wasPlaying) this.video.play().catch(() => {
1320
- });
1321
- if (!this.seeking) this._updateProgress();
1322
- };
1323
- this.video.addEventListener("seeked", finish, { once: true });
1324
- this.video.addEventListener("canplay", finish, { once: true });
1325
- setTimeout(finish, 4e3);
1326
- try {
1327
- this.video.currentTime = sec;
1328
- } catch (_) {
1329
- finish();
1330
- }
1331
- if (this.audioEl) {
1332
- try {
1333
- this.audioEl.currentTime = sec;
1334
- } catch (_) {
1335
- }
1336
- }
1337
- }
1338
- _showUI() {
1339
- if (this.uiLocked) return;
1340
- this.uiVisible = true;
1341
- this.ui?.classList.remove("hidden");
1342
- clearTimeout(this.hideTimer);
1343
- if (this.seeking || this._anySheetOpen()) return;
1344
- this.hideTimer = setTimeout(() => {
1345
- if (!this._anySheetOpen() && !this.seeking) this._hideUI();
1346
- }, 3500);
1347
- }
1348
- _hideUI() {
1349
- if (this.uiLocked || this.seeking) return;
1350
- if (this._anySheetOpen()) return;
1351
- this.uiVisible = false;
1352
- this.ui?.classList.add("hidden");
1353
- clearTimeout(this.hideTimer);
1354
- }
1355
- _toggleUI() {
1356
- if (this.uiLocked) return;
1357
- if (this.uiVisible) this._hideUI();
1358
- else this._showUI();
1359
- }
1360
- _anySheetOpen() {
1361
- return !!this.el.querySelector(".vyr-sheet.show");
1362
- }
1363
- _closeSheets() {
1364
- $$(this.el, ".vyr-sheet").forEach((s) => s.classList.remove("show"));
1365
- this.backdrop?.classList.remove("show");
1366
- }
1367
- _openSheet(sheet) {
1368
- if (!sheet || this.uiLocked) return;
1369
- this._closeSheets();
1370
- sheet.classList.add("show");
1371
- this.backdrop?.classList.add("show");
1372
- this._showUI();
1373
- }
1374
- _applySettingsUI() {
1375
- const seekVal = this.el.querySelector(".vyr-set-seek-val");
1376
- if (seekVal) seekVal.textContent = this.seekStep + "s";
1377
- const speedVal = this.el.querySelector(".vyr-set-speed-val");
1378
- if (speedVal) speedVal.textContent = fmtRate(this.playRate);
1379
- const holdVal = this.el.querySelector(".vyr-set-hold-val");
1380
- if (holdVal) holdVal.textContent = fmtRate(this.holdRate);
1381
- const br = this.el.querySelector(".vyr-badge-rew");
1382
- const bf = this.el.querySelector(".vyr-badge-ff");
1383
- if (br) br.textContent = String(this.seekStep);
1384
- if (bf) bf.textContent = String(this.seekStep);
1385
- try {
1386
- this.video.playbackRate = this.playRate;
1387
- } catch (_) {
1388
- }
1389
- this._applyVolume(false);
1390
- }
1391
- _renderQualities() {
1392
- if (!this.qualityList || !this.btnQuality) return;
1393
- const qs = this.engine.getQualities();
1394
- if (!qs.length) {
1395
- this.btnQuality.style.display = "none";
1396
- return;
1397
- }
1398
- this.btnQuality.style.display = "";
1399
- this.btnQuality.textContent = this.engine.currentQuality || qs[0].label;
1400
- this.qualityList.innerHTML = qs.map(
1401
- (q) => `<div class="vyr-sheet-item${q.label === this.engine.currentQuality ? " active" : ""}" data-label="${q.label}">${q.label}<span class="check">\u2713</span></div>`
1402
- ).join("");
1403
- }
1404
- _renderSeekList() {
1405
- if (!this.seekList) return;
1406
- this.seekList.innerHTML = SEEK_STEPS.map(
1407
- (n) => `<div class="vyr-sheet-item${n === this.seekStep ? " active" : ""}" data-step="${n}">${n} detik<span class="check">\u2713</span></div>`
1408
- ).join("");
1409
- }
1410
- _renderSpeedList() {
1411
- if (!this.speedList) return;
1412
- this.speedList.innerHTML = SPEED_OPTS.map(
1413
- (r) => `<div class="vyr-sheet-item${r === this.playRate ? " active" : ""}" data-rate="${r}">${fmtRate(r)}<span class="check">\u2713</span></div>`
1414
- ).join("");
1415
- }
1416
- _renderHoldList() {
1417
- if (!this.holdList) return;
1418
- this.holdList.innerHTML = HOLD_SPEED_OPTS.map(
1419
- (r) => `<div class="vyr-sheet-item${r === this.holdRate ? " active" : ""}" data-rate="${r}">${fmtRate(r)}<span class="check">\u2713</span></div>`
1420
- ).join("");
1421
- const tog = this.el.querySelector("[data-tog-hold]");
1422
- if (tog) tog.classList.toggle("on", this.holdSpeedOn);
1423
- }
1424
- _setSeekStep(n) {
1425
- n = clamp(Math.round(Number(n)), 1, 300);
1426
- this.seekStep = n;
1427
- this._save(LS.seek, n);
1428
- this._applySettingsUI();
1429
- this._renderSeekList();
1430
- }
1431
- _applyVolume(showToast) {
1432
- try {
1433
- if (this.audioEl) {
1434
- this.video.muted = true;
1435
- this.audioEl.volume = this.volMuted ? 0 : this.volLevel;
1436
- this.audioEl.muted = !!this.volMuted;
1437
- } else {
1438
- this.video.volume = this.volMuted ? 0 : this.volLevel;
1439
- this.video.muted = !!this.volMuted;
1440
- }
1441
- } catch (_) {
1442
- }
1443
- const sl = this.el.querySelector(".vyr-vol-slider");
1444
- if (sl) sl.value = String(Math.round((this.volMuted ? 0 : this.volLevel) * 100));
1445
- const on = this.el.querySelector(".vyr-ic-vol-on");
1446
- const off = this.el.querySelector(".vyr-ic-vol-off");
1447
- const muted = this.volMuted || this.volLevel <= 1e-3;
1448
- if (on) on.style.display = muted ? "none" : "block";
1449
- if (off) off.style.display = muted ? "block" : "none";
1450
- this._save(LS.vol, this.volLevel);
1451
- this._save(LS.muted, this.volMuted ? "1" : "0");
1452
- if (showToast && this.volToast) {
1453
- const tx = this.volToast.querySelector(".vyr-vol-toast-text");
1454
- if (tx) tx.textContent = (this.volMuted ? 0 : Math.round(this.volLevel * 100)) + "%";
1455
- this.volToast.classList.add("show");
1456
- clearTimeout(this._volT);
1457
- this._volT = setTimeout(() => this.volToast.classList.remove("show"), 900);
1458
- }
1459
- }
1460
- // ─── Bind events ──────────────────────────────────────────
1461
- _bindUI() {
1462
- const r = this.el;
1463
- this.btnPP?.addEventListener("click", (e) => {
1464
- e.stopPropagation();
1465
- this.toggle();
1466
- this._showUI();
1467
- });
1468
- this.btnRew?.addEventListener("click", (e) => {
1469
- e.stopPropagation();
1470
- if (this.uiLocked) return;
1471
- this._safeSeek((this.video.currentTime || 0) - this.seekStep);
1472
- this._toastSeek("left");
1473
- this._showUI();
1474
- });
1475
- this.btnFf?.addEventListener("click", (e) => {
1476
- e.stopPropagation();
1477
- if (this.uiLocked) return;
1478
- this._safeSeek((this.video.currentTime || 0) + this.seekStep);
1479
- this._toastSeek("right");
1480
- this._showUI();
1481
- });
1482
- this._bindDoubleTap($(r, ".vyr-tap-left"), -1);
1483
- this._bindDoubleTap($(r, ".vyr-tap-right"), 1);
1484
- this._bindProgress();
1485
- this.btnFs?.addEventListener("click", (e) => {
1486
- e.stopPropagation();
1487
- this._toggleFs();
1488
- this._showUI();
1489
- });
1490
- document.addEventListener("fullscreenchange", () => this._syncFs());
1491
- document.addEventListener("webkitfullscreenchange", () => this._syncFs());
1492
- this.btnLock?.addEventListener("click", (e) => {
1493
- e.stopPropagation();
1494
- this.uiLocked = !this.uiLocked;
1495
- this._closeSheets();
1496
- this._updateLockIcon();
1497
- if (!this.uiLocked) this._showUI();
1498
- });
1499
- this.btnSettings?.addEventListener("click", (e) => {
1500
- e.stopPropagation();
1501
- if (this.uiLocked) return;
1502
- this._openSheet(this.sheetSettings);
1503
- });
1504
- this.btnQuality?.addEventListener("click", (e) => {
1505
- e.stopPropagation();
1506
- if (this.uiLocked) return;
1507
- this._renderQualities();
1508
- this._openSheet(this.sheetQuality);
1509
- });
1510
- this.btnCc?.addEventListener("click", (e) => {
1511
- e.stopPropagation();
1512
- if (this.uiLocked) return;
1513
- this._renderSubsList();
1514
- this._applySubStyle();
1515
- this._openSheet(this.sheetSubs);
1516
- });
1517
- this.subsList?.addEventListener("click", (e) => {
1518
- const item = e.target.closest("[data-sub-idx]");
1519
- if (!item) return;
1520
- const idx = parseInt(item.dataset.subIdx, 10);
1521
- if (idx < 0) this._disableAllTracks();
1522
- else this._enableTrack(idx);
1523
- this._showUI();
1524
- });
1525
- this.el.querySelector("[data-sub-toggle]")?.addEventListener("click", (e) => {
1526
- e.stopPropagation();
1527
- this.subOn = !this.subOn;
1528
- this._save(LS.subOn, this.subOn ? "1" : "0");
1529
- this._applySubStyle();
1530
- });
1531
- this.el.querySelector(".vyr-sub-y")?.addEventListener("input", (e) => {
1532
- this.subY = parseInt(e.target.value, 10) || 0;
1533
- this._save(LS.subY, this.subY);
1534
- this._applySubStyle();
1535
- });
1536
- this.el.querySelector(".vyr-sub-color")?.addEventListener("input", (e) => {
1537
- this.subColor = e.target.value;
1538
- this._save(LS.subColor, this.subColor);
1539
- this._applySubStyle();
1540
- });
1541
- this.el.querySelector(".vyr-sub-size")?.addEventListener("input", (e) => {
1542
- this.subSize = parseInt(e.target.value, 10) || 20;
1543
- this._save(LS.subSize, this.subSize);
1544
- this._applySubStyle();
1545
- });
1546
- this.el.querySelector(".vyr-sub-bg")?.addEventListener("input", (e) => {
1547
- this.subBg = parseInt(e.target.value, 10) || 0;
1548
- this._save(LS.subBg, this.subBg);
1549
- this._applySubStyle();
1550
- });
1551
- const closeOverlay = (e) => {
1552
- if (e) e.stopPropagation();
1553
- this._closeSheets();
1554
- this._showUI();
1555
- };
1556
- this.backdrop?.addEventListener("click", closeOverlay);
1557
- this.backdrop?.addEventListener("touchend", (e) => {
1558
- if (e.target === this.backdrop) closeOverlay(e);
1559
- }, { passive: false });
1560
- $$(r, "[data-close]").forEach((btn) => {
1561
- btn.addEventListener("click", closeOverlay);
1562
- btn.addEventListener("touchend", (e) => {
1563
- e.preventDefault();
1564
- closeOverlay(e);
1565
- }, { passive: false });
1566
- });
1567
- this.qualityList?.addEventListener("click", (e) => {
1568
- const item = e.target.closest("[data-label]");
1569
- if (!item) return;
1570
- this.engine.switchQuality(item.dataset.label);
1571
- this._closeSheets();
1572
- this._showUI();
1573
- });
1574
- r.querySelector('[data-set="seek"]')?.addEventListener("click", () => {
1575
- this._renderSeekList();
1576
- this._openSheet(this.sheetSeek);
1577
- });
1578
- r.querySelector('[data-set="speed"]')?.addEventListener("click", () => {
1579
- this._renderSpeedList();
1580
- this._openSheet(this.sheetSpeed);
1581
- });
1582
- r.querySelector('[data-set="hold"]')?.addEventListener("click", () => {
1583
- this._renderHoldList();
1584
- this._openSheet(this.sheetHold);
1585
- });
1586
- r.querySelector('[data-set="substyle"]')?.addEventListener("click", () => {
1587
- this._applySubStyle();
1588
- this._openSheet(this.sheetSubstyle);
1589
- });
1590
- this.seekList?.addEventListener("click", (e) => {
1591
- const item = e.target.closest("[data-step]");
1592
- if (!item) return;
1593
- this._setSeekStep(parseInt(item.dataset.step, 10));
1594
- this._closeSheets();
1595
- this._showUI();
1596
- });
1597
- this.speedList?.addEventListener("click", (e) => {
1598
- const item = e.target.closest("[data-rate]");
1599
- if (!item) return;
1600
- this.setPlaybackRate(parseFloat(item.dataset.rate));
1601
- this._closeSheets();
1602
- this._showUI();
1603
- });
1604
- this.holdList?.addEventListener("click", (e) => {
1605
- const item = e.target.closest("[data-rate]");
1606
- if (!item) return;
1607
- this.holdRate = parseFloat(item.dataset.rate);
1608
- this._save(LS.holdRate, this.holdRate);
1609
- const el = r.querySelector(".vyr-set-hold-val");
1610
- if (el) el.textContent = fmtRate(this.holdRate);
1611
- this._renderHoldList();
1612
- });
1613
- r.querySelector("[data-hold-toggle]")?.addEventListener("click", () => {
1614
- this.holdSpeedOn = !this.holdSpeedOn;
1615
- this._save(LS.holdOn, this.holdSpeedOn ? "1" : "0");
1616
- this._renderHoldList();
1617
- });
1618
- r.querySelector(".vyr-btn-vol")?.addEventListener("click", (e) => {
1619
- e.stopPropagation();
1620
- this.volMuted = !this.volMuted;
1621
- this._applyVolume(true);
1622
- this._showUI();
1623
- });
1624
- r.querySelector(".vyr-vol-slider")?.addEventListener("input", (e) => {
1625
- e.stopPropagation();
1626
- this.volLevel = parseInt(e.target.value, 10) / 100;
1627
- if (this.volLevel > 1e-3) this.volMuted = false;
1628
- this._applyVolume(true);
1629
- });
1630
- this.btnPip?.addEventListener("click", async (e) => {
1631
- e.stopPropagation();
1632
- if (this.uiLocked) return;
1633
- try {
1634
- if (document.pictureInPictureElement) await document.exitPictureInPicture();
1635
- else {
1636
- if (this.video.paused) await this.video.play();
1637
- await this.video.requestPictureInPicture();
1638
- }
1639
- } catch (_) {
1640
- }
1641
- this._showUI();
1642
- });
1643
- r.querySelector(".vyr-btn-pip-back")?.addEventListener("click", async () => {
1644
- try {
1645
- if (document.pictureInPictureElement) await document.exitPictureInPicture();
1646
- } catch (_) {
1647
- }
1648
- });
1649
- this.video?.addEventListener("enterpictureinpicture", () => this.pipOverlay?.classList.add("show"));
1650
- this.video?.addEventListener("leavepictureinpicture", () => this.pipOverlay?.classList.remove("show"));
1651
- if (this.opts.features.holdToSpeed !== false) {
1652
- r.addEventListener("touchstart", (e) => this._onHoldStart(e), { passive: true });
1653
- r.addEventListener("touchend", () => this._onHoldEnd());
1654
- r.addEventListener("touchcancel", () => this._onHoldEnd());
1655
- r.addEventListener("mousedown", (e) => this._onHoldStart(e));
1656
- r.addEventListener("mouseup", () => this._onHoldEnd());
1657
- r.addEventListener("mouseleave", () => this._onHoldEnd());
1658
- }
1659
- r.addEventListener("click", (e) => {
1660
- if (e.target.closest(".vyr-btn-lock")) return;
1661
- if (this.uiLocked) return;
1662
- if (e.target.closest("button") || e.target.closest(".vyr-sheet") || e.target.closest(".vyr-progress") || e.target.closest(".vyr-chip") || e.target.closest(".vyr-vol-wrap")) return;
1663
- if (this._anySheetOpen()) {
1664
- this._closeSheets();
1665
- return;
1666
- }
1667
- this._toggleUI();
1668
- });
1669
- this.video.addEventListener("play", () => {
1670
- this.poster?.classList.add("hide");
1671
- this._updatePlayIcon();
1672
- this._showUI();
1673
- this._audioPlay();
1674
- this.emitter.emit("play", this.getState());
1675
- });
1676
- this.video.addEventListener("pause", () => {
1677
- this._updatePlayIcon();
1678
- this._showUI();
1679
- this._audioPause();
1680
- this.emitter.emit("pause", this.getState());
1681
- });
1682
- this.video.addEventListener("timeupdate", () => {
1683
- this._updateProgress();
1684
- this._syncManualSubs();
1685
- if (this._syncAudioTime) this._syncAudioTime();
1686
- if (!this.video.paused && this.video.readyState >= 2) {
1687
- if (this.buffer && this.buffer.classList.contains("show")) {
1688
- clearTimeout(this._bufT);
1689
- this._hideBuffer();
1690
- }
1691
- }
1692
- this.emitter.emit("timeupdate", this.getState());
1693
- });
1694
- this.video.addEventListener("ended", () => {
1695
- this.emitter.emit("ended", this.getState());
1696
- });
1697
- this.video.addEventListener("waiting", () => {
1698
- clearTimeout(this._bufT);
1699
- this._bufT = setTimeout(() => {
1700
- if (this._destroyed || !this.video) return;
1701
- if (this.video.paused) return;
1702
- if (this.video.readyState >= 3) return;
1703
- try {
1704
- const t = this.video.currentTime || 0;
1705
- const buf = this.video.buffered;
1706
- for (let i = 0; i < buf.length; i++) {
1707
- if (t >= buf.start(i) - 0.15 && t < buf.end(i) - 0.35) return;
1708
- }
1709
- } catch (_) {
1710
- }
1711
- this._showBuffer();
1712
- }, 500);
1713
- });
1714
- this.video.addEventListener("playing", () => {
1715
- clearTimeout(this._bufT);
1716
- this._hideBuffer();
1717
- });
1718
- this.video.addEventListener("canplay", () => {
1719
- clearTimeout(this._bufT);
1720
- this._hideBuffer();
1721
- });
1722
- this.video.addEventListener("canplaythrough", () => {
1723
- clearTimeout(this._bufT);
1724
- this._hideBuffer();
1725
- });
1726
- this.video.addEventListener("seeked", () => {
1727
- clearTimeout(this._bufT);
1728
- if (!this.video.paused) this._hideBuffer();
1729
- });
1730
- document.addEventListener("keydown", (e) => {
1731
- if (this._destroyed) return;
1732
- if (e.key === "Escape" || e.code === "Escape") {
1733
- if (this._anySheetOpen()) {
1734
- e.preventDefault();
1735
- this._closeSheets();
1736
- this._showUI();
1737
- }
1738
- return;
1739
- }
1740
- if (e.code === "Space") {
1741
- e.preventDefault();
1742
- this.toggle();
1743
- this._showUI();
1744
- } else if (e.code === "ArrowLeft") {
1745
- if (this.uiLocked) return;
1746
- this._safeSeek((this.video.currentTime || 0) - this.seekStep);
1747
- this._toastSeek("left");
1748
- this._showUI();
1749
- } else if (e.code === "ArrowRight") {
1750
- if (this.uiLocked) return;
1751
- this._safeSeek((this.video.currentTime || 0) + this.seekStep);
1752
- this._toastSeek("right");
1753
- this._showUI();
1754
- }
1755
- });
1756
- r.addEventListener("contextmenu", (e) => {
1757
- if (e.target.closest("video") || e.target.closest(".vyr-root")) e.preventDefault();
1758
- });
1759
- this._showUI();
1760
- }
1761
- _bindDoubleTap(zone, dir) {
1762
- if (!zone) return;
1763
- let last = 0;
1764
- zone.addEventListener("touchend", (e) => {
1765
- const now = Date.now();
1766
- if (now - last < 300) {
1767
- if (this.uiLocked) return;
1768
- this._safeSeek((this.video.currentTime || 0) + dir * this.seekStep);
1769
- this._toastSeek(dir < 0 ? "left" : "right");
1770
- e.preventDefault();
1771
- this._showUI();
1772
- }
1773
- last = now;
1774
- }, { passive: false });
1775
- }
1776
- _bindProgress() {
1777
- if (!this.progress) return;
1778
- let scrubTarget = 0;
1779
- const pctFrom = (e) => {
1780
- const rect = this.progress.getBoundingClientRect();
1781
- const clientX = e.touches?.[0]?.clientX ?? e.changedTouches?.[0]?.clientX ?? e.clientX;
1782
- return clamp((clientX - rect.left) / rect.width, 0, 1);
1783
- };
1784
- const preview = (e) => {
1785
- const pct = pctFrom(e);
1786
- const d = isFinite(this.video.duration) ? this.video.duration : 0;
1787
- scrubTarget = d * pct;
1788
- this._paintProgress(scrubTarget, d, true);
1789
- this.uiVisible = true;
1790
- this.ui?.classList.remove("hidden");
1791
- clearTimeout(this.hideTimer);
1792
- };
1793
- const end = (e) => {
1794
- if (!this.seeking) return;
1795
- this.seeking = false;
1796
- this.progress.classList.remove("scrubbing");
1797
- const pct = e ? pctFrom(e) : null;
1798
- const d = isFinite(this.video.duration) ? this.video.duration : 0;
1799
- const t = pct != null && d ? d * pct : scrubTarget;
1800
- this.scrubTip?.classList.remove("show");
1801
- if (d > 0) this._safeSeek(t);
1802
- this._showUI();
1803
- };
1804
- this.progress.addEventListener("click", (e) => {
1805
- e.stopPropagation();
1806
- if (this.seeking) return;
1807
- const pct = pctFrom(e);
1808
- const d = isFinite(this.video.duration) ? this.video.duration : 0;
1809
- if (d > 0) this._safeSeek(d * pct);
1810
- this._showUI();
1811
- });
1812
- this.progress.addEventListener("touchstart", (e) => {
1813
- this.seeking = true;
1814
- this.progress.classList.add("scrubbing");
1815
- e.stopPropagation();
1816
- preview(e);
1817
- }, { passive: true });
1818
- this.progress.addEventListener("touchmove", (e) => {
1819
- if (this.seeking) preview(e);
1820
- }, { passive: true });
1821
- this.progress.addEventListener("touchend", end);
1822
- this.progress.addEventListener("touchcancel", end);
1823
- this.progress.addEventListener("mousedown", (e) => {
1824
- if (e.button !== 0) return;
1825
- this.seeking = true;
1826
- this.progress.classList.add("scrubbing");
1827
- e.preventDefault();
1828
- preview(e);
1829
- const onMove = (ev) => {
1830
- if (this.seeking) preview(ev);
1831
- };
1832
- const onUp = (ev) => {
1833
- document.removeEventListener("mousemove", onMove);
1834
- document.removeEventListener("mouseup", onUp);
1835
- end(ev);
1836
- };
1837
- document.addEventListener("mousemove", onMove);
1838
- document.addEventListener("mouseup", onUp);
1839
- });
1840
- }
1841
- _toastSeek(side) {
1842
- const el = this.el.querySelector(side === "left" ? ".vyr-seek-left" : ".vyr-seek-right");
1843
- if (!el) return;
1844
- const sec = el.querySelector(".sec");
1845
- if (sec) sec.textContent = side === "left" ? "\xAB" + this.seekStep : this.seekStep + "\xBB";
1846
- el.classList.remove("show");
1847
- void el.offsetWidth;
1848
- el.classList.add("show");
1849
- clearTimeout(el._t);
1850
- el._t = setTimeout(() => el.classList.remove("show"), 700);
1851
- }
1852
- _onHoldStart(e) {
1853
- if (!this.holdSpeedOn || this.uiLocked || this.video.paused) return;
1854
- if (e.target.closest("button") || e.target.closest(".vyr-sheet") || e.target.closest(".vyr-progress") || e.target.closest(".vyr-chip")) return;
1855
- clearTimeout(this.holdTimer);
1856
- this.holdTimer = setTimeout(() => {
1857
- this.isHoldingSpeed = true;
1858
- try {
1859
- this.video.playbackRate = this.holdRate;
1860
- } catch (_) {
1861
- }
1862
- if (this.speedBadge) {
1863
- this.speedBadge.textContent = fmtRate(this.holdRate);
1864
- this.speedBadge.classList.add("show");
1865
- }
1866
- }, 320);
1867
- }
1868
- _onHoldEnd() {
1869
- clearTimeout(this.holdTimer);
1870
- if (!this.isHoldingSpeed) return;
1871
- this.isHoldingSpeed = false;
1872
- try {
1873
- this.video.playbackRate = this.playRate;
1874
- } catch (_) {
1875
- }
1876
- this.speedBadge?.classList.remove("show");
1877
- }
1878
- _updateLockIcon() {
1879
- const open = this.el.querySelector(".vyr-ic-lock-open");
1880
- const closed = this.el.querySelector(".vyr-ic-lock-closed");
1881
- if (this.uiLocked) {
1882
- if (open) open.style.display = "none";
1883
- if (closed) closed.style.display = "block";
1884
- this.btnLock?.classList.add("active-lock");
1885
- this.ui?.classList.add("ui-locked");
1886
- this.ui?.classList.remove("hidden");
1887
- } else {
1888
- if (open) open.style.display = "block";
1889
- if (closed) closed.style.display = "none";
1890
- this.btnLock?.classList.remove("active-lock");
1891
- this.ui?.classList.remove("ui-locked");
1892
- }
1893
- }
1894
- _toggleFs() {
1895
- const el = this.el;
1896
- if (!document.fullscreenElement && !document.webkitFullscreenElement) {
1897
- (el.requestFullscreen || el.webkitRequestFullscreen || (() => {
1898
- })).call(el);
1899
- try {
1900
- screen.orientation?.lock?.("landscape");
1901
- } catch (_) {
1902
- }
1903
- } else {
1904
- (document.exitFullscreen || document.webkitExitFullscreen || (() => {
1905
- })).call(document);
1906
- try {
1907
- screen.orientation?.unlock?.();
1908
- } catch (_) {
1909
- }
1910
- }
1911
- }
1912
- _syncFs() {
1913
- const on = !!(document.fullscreenElement || document.webkitFullscreenElement);
1914
- this.el.classList.toggle("is-fs", on);
1915
- }
1916
- };
1917
- export {
1918
- MediaEngine,
1919
- VyrPlay,
1920
- fmtRate,
1921
- fmtTime
1922
- };
197
+ <div class="vyr-error-detail">${t}</div>
198
+ </div>`,this.loading.classList.remove("hide"))}_updatePlayIcon(){let t=this.video.paused,s=this.el.querySelector(".vyr-ic-play"),e=this.el.querySelector(".vyr-ic-pause");s&&(s.style.display=t?"block":"none"),e&&(e.style.display=t?"none":"block")}_paintProgress(t,s,e){s=s!=null&&isFinite(s)?s:this.video?.duration||0,t=isFinite(t)?Math.max(0,t):0,s>0&&(t=Math.min(t,s));let i=s>0?t/s*100:0;this.pFill&&(this.pFill.style.width=i+"%"),this.pThumb&&(this.pThumb.style.left=i+"%"),this.timeLabel&&(this.timeLabel.textContent=k(t)+" / "+k(s)),this.scrubTip&&(this.scrubTip.textContent=k(t),this.scrubTip.style.left=i+"%",this.scrubTip.classList.toggle("show",!!e))}_updateProgress(){this.seeking||this._paintProgress(this.video.currentTime||0,this.video.duration||0,!1)}_safeSeek(t){if(!this.video||!isFinite(t))return;let s=this.video.duration;isFinite(s)&&s>0?t=w(t,0,Math.max(0,s-.3)):t=Math.max(0,t),this._paintProgress(t,s,!1);let e=!this.video.paused;this.btnPP?.classList.add("loading");let i=!1,r=()=>{i||(i=!0,this.btnPP?.classList.remove("loading"),e&&this.video.play().catch(()=>{}),this.seeking||this._updateProgress())};this.video.addEventListener("seeked",r,{once:!0}),this.video.addEventListener("canplay",r,{once:!0}),setTimeout(r,4e3);try{this.video.currentTime=t}catch{r()}if(this.audioEl)try{this.audioEl.currentTime=t}catch{}}_showUI(){this.uiLocked||(this.uiVisible=!0,this.ui?.classList.remove("hidden"),clearTimeout(this.hideTimer),!(this.seeking||this._anySheetOpen())&&(this.hideTimer=setTimeout(()=>{!this._anySheetOpen()&&!this.seeking&&this._hideUI()},3500)))}_hideUI(){this.uiLocked||this.seeking||this._anySheetOpen()||(this.uiVisible=!1,this.ui?.classList.add("hidden"),clearTimeout(this.hideTimer))}_toggleUI(){this.uiLocked||(this.uiVisible?this._hideUI():this._showUI())}_anySheetOpen(){return!!this.el.querySelector(".vyr-sheet.show")}_closeSheets(){I(this.el,".vyr-sheet").forEach(t=>t.classList.remove("show")),this.backdrop?.classList.remove("show")}_openSheet(t){!t||this.uiLocked||(this._closeSheets(),t.classList.add("show"),this.backdrop?.classList.add("show"),this._showUI())}_applySettingsUI(){let t=this.el.querySelector(".vyr-set-seek-val");t&&(t.textContent=this.seekStep+"s");let s=this.el.querySelector(".vyr-set-speed-val");s&&(s.textContent=b(this.playRate));let e=this.el.querySelector(".vyr-set-hold-val");e&&(e.textContent=b(this.holdRate));let i=this.el.querySelector(".vyr-badge-rew"),r=this.el.querySelector(".vyr-badge-ff");i&&(i.textContent=String(this.seekStep)),r&&(r.textContent=String(this.seekStep));try{this.video.playbackRate=this.playRate}catch{}this._applyVolume(!1)}_renderQualities(){if(!this.qualityList||!this.btnQuality)return;let t=this.engine.getQualities();if(!t.length){this.btnQuality.style.display="none";return}this.btnQuality.style.display="",this.btnQuality.textContent=this.engine.currentQuality||t[0].label,this.qualityList.innerHTML=t.map(s=>`<div class="vyr-sheet-item${s.label===this.engine.currentQuality?" active":""}" data-label="${s.label}">${s.label}<span class="check">\u2713</span></div>`).join("")}_renderSeekList(){this.seekList&&(this.seekList.innerHTML=H.map(t=>`<div class="vyr-sheet-item${t===this.seekStep?" active":""}" data-step="${t}">${t} detik<span class="check">\u2713</span></div>`).join(""))}_renderSpeedList(){this.speedList&&(this.speedList.innerHTML=C.map(t=>`<div class="vyr-sheet-item${t===this.playRate?" active":""}" data-rate="${t}">${b(t)}<span class="check">\u2713</span></div>`).join(""))}_renderHoldList(){if(!this.holdList)return;this.holdList.innerHTML=Q.map(s=>`<div class="vyr-sheet-item${s===this.holdRate?" active":""}" data-rate="${s}">${b(s)}<span class="check">\u2713</span></div>`).join("");let t=this.el.querySelector("[data-tog-hold]");t&&t.classList.toggle("on",this.holdSpeedOn)}_setSeekStep(t){t=w(Math.round(Number(t)),1,300),this.seekStep=t,this._save(d.seek,t),this._applySettingsUI(),this._renderSeekList()}_applyVolume(t){try{this.audioEl?(this.video.muted=!0,this.audioEl.volume=this.volMuted?0:this.volLevel,this.audioEl.muted=!!this.volMuted):(this.video.volume=this.volMuted?0:this.volLevel,this.video.muted=!!this.volMuted)}catch{}let s=this.el.querySelector(".vyr-vol-slider");s&&(s.value=String(Math.round((this.volMuted?0:this.volLevel)*100)));let e=this.el.querySelector(".vyr-ic-vol-on"),i=this.el.querySelector(".vyr-ic-vol-off"),r=this.volMuted||this.volLevel<=.001;if(e&&(e.style.display=r?"none":"block"),i&&(i.style.display=r?"block":"none"),this._save(d.vol,this.volLevel),this._save(d.muted,this.volMuted?"1":"0"),t&&this.volToast){let a=this.volToast.querySelector(".vyr-vol-toast-text");a&&(a.textContent=(this.volMuted?0:Math.round(this.volLevel*100))+"%"),this.volToast.classList.add("show"),clearTimeout(this._volT),this._volT=setTimeout(()=>this.volToast.classList.remove("show"),900)}}_bindUI(){let t=this.el;this.btnPP?.addEventListener("click",e=>{e.stopPropagation(),this.toggle(),this._showUI()}),this.btnRew?.addEventListener("click",e=>{e.stopPropagation(),!this.uiLocked&&(this._safeSeek((this.video.currentTime||0)-this.seekStep),this._toastSeek("left"),this._showUI())}),this.btnFf?.addEventListener("click",e=>{e.stopPropagation(),!this.uiLocked&&(this._safeSeek((this.video.currentTime||0)+this.seekStep),this._toastSeek("right"),this._showUI())}),this._bindDoubleTap(o(t,".vyr-tap-left"),-1),this._bindDoubleTap(o(t,".vyr-tap-right"),1),this._bindProgress(),this.btnFs?.addEventListener("click",e=>{e.stopPropagation(),this._toggleFs(),this._showUI()}),document.addEventListener("fullscreenchange",()=>this._syncFs()),document.addEventListener("webkitfullscreenchange",()=>this._syncFs()),this.btnLock?.addEventListener("click",e=>{e.stopPropagation(),this.uiLocked=!this.uiLocked,this._closeSheets(),this._updateLockIcon(),this.uiLocked||this._showUI()}),this.btnSettings?.addEventListener("click",e=>{e.stopPropagation(),!this.uiLocked&&this._openSheet(this.sheetSettings)}),this.btnQuality?.addEventListener("click",e=>{e.stopPropagation(),!this.uiLocked&&(this._renderQualities(),this._openSheet(this.sheetQuality))}),this.btnCc?.addEventListener("click",e=>{e.stopPropagation(),!this.uiLocked&&(this._renderSubsList(),this._applySubStyle(),this._openSheet(this.sheetSubs))}),this.subsList?.addEventListener("click",e=>{let i=e.target.closest("[data-sub-idx]");if(!i)return;let r=parseInt(i.dataset.subIdx,10);r<0?this._disableAllTracks():this._enableTrack(r),this._showUI()}),this.el.querySelector("[data-sub-toggle]")?.addEventListener("click",e=>{e.stopPropagation(),this.subOn=!this.subOn,this._save(d.subOn,this.subOn?"1":"0"),this._applySubStyle()}),this.el.querySelector(".vyr-sub-y")?.addEventListener("input",e=>{this.subY=parseInt(e.target.value,10)||0,this._save(d.subY,this.subY),this._applySubStyle()}),this.el.querySelector(".vyr-sub-color")?.addEventListener("input",e=>{this.subColor=e.target.value,this._save(d.subColor,this.subColor),this._applySubStyle()}),this.el.querySelector(".vyr-sub-size")?.addEventListener("input",e=>{this.subSize=parseInt(e.target.value,10)||20,this._save(d.subSize,this.subSize),this._applySubStyle()}),this.el.querySelector(".vyr-sub-bg")?.addEventListener("input",e=>{this.subBg=parseInt(e.target.value,10)||0,this._save(d.subBg,this.subBg),this._applySubStyle()});let s=e=>{e&&e.stopPropagation(),this._closeSheets(),this._showUI()};this.backdrop?.addEventListener("click",s),this.backdrop?.addEventListener("touchend",e=>{e.target===this.backdrop&&s(e)},{passive:!1}),I(t,"[data-close]").forEach(e=>{e.addEventListener("click",s),e.addEventListener("touchend",i=>{i.preventDefault(),s(i)},{passive:!1})}),this.qualityList?.addEventListener("click",e=>{let i=e.target.closest("[data-label]");i&&(this.engine.switchQuality(i.dataset.label),this._closeSheets(),this._showUI())}),t.querySelector('[data-set="seek"]')?.addEventListener("click",()=>{this._renderSeekList(),this._openSheet(this.sheetSeek)}),t.querySelector('[data-set="speed"]')?.addEventListener("click",()=>{this._renderSpeedList(),this._openSheet(this.sheetSpeed)}),t.querySelector('[data-set="hold"]')?.addEventListener("click",()=>{this._renderHoldList(),this._openSheet(this.sheetHold)}),t.querySelector('[data-set="substyle"]')?.addEventListener("click",()=>{this._applySubStyle(),this._openSheet(this.sheetSubstyle)}),this.seekList?.addEventListener("click",e=>{let i=e.target.closest("[data-step]");i&&(this._setSeekStep(parseInt(i.dataset.step,10)),this._closeSheets(),this._showUI())}),this.speedList?.addEventListener("click",e=>{let i=e.target.closest("[data-rate]");i&&(this.setPlaybackRate(parseFloat(i.dataset.rate)),this._closeSheets(),this._showUI())}),this.holdList?.addEventListener("click",e=>{let i=e.target.closest("[data-rate]");if(!i)return;this.holdRate=parseFloat(i.dataset.rate),this._save(d.holdRate,this.holdRate);let r=t.querySelector(".vyr-set-hold-val");r&&(r.textContent=b(this.holdRate)),this._renderHoldList()}),t.querySelector("[data-hold-toggle]")?.addEventListener("click",()=>{this.holdSpeedOn=!this.holdSpeedOn,this._save(d.holdOn,this.holdSpeedOn?"1":"0"),this._renderHoldList()}),t.querySelector(".vyr-btn-vol")?.addEventListener("click",e=>{e.stopPropagation(),this.volMuted=!this.volMuted,this._applyVolume(!0),this._showUI()}),t.querySelector(".vyr-vol-slider")?.addEventListener("input",e=>{e.stopPropagation(),this.volLevel=parseInt(e.target.value,10)/100,this.volLevel>.001&&(this.volMuted=!1),this._applyVolume(!0)}),this.btnPip?.addEventListener("click",async e=>{if(e.stopPropagation(),!this.uiLocked){try{document.pictureInPictureElement?await document.exitPictureInPicture():(this.video.paused&&await this.video.play(),await this.video.requestPictureInPicture())}catch{}this._showUI()}}),t.querySelector(".vyr-btn-pip-back")?.addEventListener("click",async()=>{try{document.pictureInPictureElement&&await document.exitPictureInPicture()}catch{}}),this.video?.addEventListener("enterpictureinpicture",()=>this.pipOverlay?.classList.add("show")),this.video?.addEventListener("leavepictureinpicture",()=>this.pipOverlay?.classList.remove("show")),this.opts.features.holdToSpeed!==!1&&(t.addEventListener("touchstart",e=>this._onHoldStart(e),{passive:!0}),t.addEventListener("touchend",()=>this._onHoldEnd()),t.addEventListener("touchcancel",()=>this._onHoldEnd()),t.addEventListener("mousedown",e=>this._onHoldStart(e)),t.addEventListener("mouseup",()=>this._onHoldEnd()),t.addEventListener("mouseleave",()=>this._onHoldEnd())),t.addEventListener("click",e=>{if(!e.target.closest(".vyr-btn-lock")&&!this.uiLocked&&!(e.target.closest("button")||e.target.closest(".vyr-sheet")||e.target.closest(".vyr-progress")||e.target.closest(".vyr-chip")||e.target.closest(".vyr-vol-wrap"))){if(this._anySheetOpen()){this._closeSheets();return}this._toggleUI()}}),this.video.addEventListener("play",()=>{this.poster?.classList.add("hide"),this._updatePlayIcon(),this._showUI(),this._audioPlay(),this.emitter.emit("play",this.getState())}),this.video.addEventListener("pause",()=>{this._updatePlayIcon(),this._showUI(),this._audioPause(),this.emitter.emit("pause",this.getState())}),this.video.addEventListener("timeupdate",()=>{this._updateProgress(),this._syncManualSubs(),this._syncAudioTime&&this._syncAudioTime(),!this.video.paused&&this.video.readyState>=2&&this.buffer&&this.buffer.classList.contains("show")&&(clearTimeout(this._bufT),this._hideBuffer()),this.emitter.emit("timeupdate",this.getState())}),this.video.addEventListener("ended",()=>{this.emitter.emit("ended",this.getState())}),this.video.addEventListener("waiting",()=>{clearTimeout(this._bufT),this._bufT=setTimeout(()=>{if(!(this._destroyed||!this.video)&&!this.video.paused&&!(this.video.readyState>=3)){try{let e=this.video.currentTime||0,i=this.video.buffered;for(let r=0;r<i.length;r++)if(e>=i.start(r)-.15&&e<i.end(r)-.35)return}catch{}this._showBuffer()}},500)}),this.video.addEventListener("playing",()=>{clearTimeout(this._bufT),this._hideBuffer()}),this.video.addEventListener("canplay",()=>{clearTimeout(this._bufT),this._hideBuffer()}),this.video.addEventListener("canplaythrough",()=>{clearTimeout(this._bufT),this._hideBuffer()}),this.video.addEventListener("seeked",()=>{clearTimeout(this._bufT),this.video.paused||this._hideBuffer()}),document.addEventListener("keydown",e=>{if(!this._destroyed){if(e.key==="Escape"||e.code==="Escape"){this._anySheetOpen()&&(e.preventDefault(),this._closeSheets(),this._showUI());return}if(e.code==="Space")e.preventDefault(),this.toggle(),this._showUI();else if(e.code==="ArrowLeft"){if(this.uiLocked)return;this._safeSeek((this.video.currentTime||0)-this.seekStep),this._toastSeek("left"),this._showUI()}else if(e.code==="ArrowRight"){if(this.uiLocked)return;this._safeSeek((this.video.currentTime||0)+this.seekStep),this._toastSeek("right"),this._showUI()}}}),t.addEventListener("contextmenu",e=>{(e.target.closest("video")||e.target.closest(".vyr-root"))&&e.preventDefault()}),this._showUI()}_bindDoubleTap(t,s){if(!t)return;let e=0;t.addEventListener("touchend",i=>{let r=Date.now();if(r-e<300){if(this.uiLocked)return;this._safeSeek((this.video.currentTime||0)+s*this.seekStep),this._toastSeek(s<0?"left":"right"),i.preventDefault(),this._showUI()}e=r},{passive:!1})}_bindProgress(){if(!this.progress)return;let t=0,s=r=>{let a=this.progress.getBoundingClientRect(),l=r.touches?.[0]?.clientX??r.changedTouches?.[0]?.clientX??r.clientX;return w((l-a.left)/a.width,0,1)},e=r=>{let a=s(r),l=isFinite(this.video.duration)?this.video.duration:0;t=l*a,this._paintProgress(t,l,!0),this.uiVisible=!0,this.ui?.classList.remove("hidden"),clearTimeout(this.hideTimer)},i=r=>{if(!this.seeking)return;this.seeking=!1,this.progress.classList.remove("scrubbing");let a=r?s(r):null,l=isFinite(this.video.duration)?this.video.duration:0,n=a!=null&&l?l*a:t;this.scrubTip?.classList.remove("show"),l>0&&this._safeSeek(n),this._showUI()};this.progress.addEventListener("click",r=>{if(r.stopPropagation(),this.seeking)return;let a=s(r),l=isFinite(this.video.duration)?this.video.duration:0;l>0&&this._safeSeek(l*a),this._showUI()}),this.progress.addEventListener("touchstart",r=>{this.seeking=!0,this.progress.classList.add("scrubbing"),r.stopPropagation(),e(r)},{passive:!0}),this.progress.addEventListener("touchmove",r=>{this.seeking&&e(r)},{passive:!0}),this.progress.addEventListener("touchend",i),this.progress.addEventListener("touchcancel",i),this.progress.addEventListener("mousedown",r=>{if(r.button!==0)return;this.seeking=!0,this.progress.classList.add("scrubbing"),r.preventDefault(),e(r);let a=n=>{this.seeking&&e(n)},l=n=>{document.removeEventListener("mousemove",a),document.removeEventListener("mouseup",l),i(n)};document.addEventListener("mousemove",a),document.addEventListener("mouseup",l)})}_toastSeek(t){let s=this.el.querySelector(t==="left"?".vyr-seek-left":".vyr-seek-right");if(!s)return;let e=s.querySelector(".sec");e&&(e.textContent=t==="left"?"\xAB"+this.seekStep:this.seekStep+"\xBB"),s.classList.remove("show"),s.offsetWidth,s.classList.add("show"),clearTimeout(s._t),s._t=setTimeout(()=>s.classList.remove("show"),700)}_onHoldStart(t){!this.holdSpeedOn||this.uiLocked||this.video.paused||t.target.closest("button")||t.target.closest(".vyr-sheet")||t.target.closest(".vyr-progress")||t.target.closest(".vyr-chip")||(clearTimeout(this.holdTimer),this.holdTimer=setTimeout(()=>{this.isHoldingSpeed=!0;try{this.video.playbackRate=this.holdRate}catch{}this.speedBadge&&(this.speedBadge.textContent=b(this.holdRate),this.speedBadge.classList.add("show"))},320))}_onHoldEnd(){if(clearTimeout(this.holdTimer),!!this.isHoldingSpeed){this.isHoldingSpeed=!1;try{this.video.playbackRate=this.playRate}catch{}this.speedBadge?.classList.remove("show")}}_updateLockIcon(){let t=this.el.querySelector(".vyr-ic-lock-open"),s=this.el.querySelector(".vyr-ic-lock-closed");this.uiLocked?(t&&(t.style.display="none"),s&&(s.style.display="block"),this.btnLock?.classList.add("active-lock"),this.ui?.classList.add("ui-locked"),this.ui?.classList.remove("hidden")):(t&&(t.style.display="block"),s&&(s.style.display="none"),this.btnLock?.classList.remove("active-lock"),this.ui?.classList.remove("ui-locked"))}_toggleFs(){let t=this.el;if(!document.fullscreenElement&&!document.webkitFullscreenElement){(t.requestFullscreen||t.webkitRequestFullscreen||(()=>{})).call(t);try{screen.orientation?.lock?.("landscape")}catch{}}else{(document.exitFullscreen||document.webkitExitFullscreen||(()=>{})).call(document);try{screen.orientation?.unlock?.()}catch{}}}_syncFs(){let t=!!(document.fullscreenElement||document.webkitFullscreenElement);this.el.classList.toggle("is-fs",t)}};export{m as MediaEngine,x as VyrPlay,b as fmtRate,k as fmtTime};