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