adgent-sdk 0.1.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.
@@ -0,0 +1,1420 @@
1
+ var R = Object.defineProperty;
2
+ var S = (i, e, r) => e in i ? R(i, e, { enumerable: !0, configurable: !0, writable: !0, value: r }) : i[e] = r;
3
+ var l = (i, e, r) => S(i, typeof e != "symbol" ? e + "" : e, r);
4
+ import { XMLParser as T } from "fast-xml-parser";
5
+ var d = /* @__PURE__ */ ((i) => (i.Idle = "idle", i.Loading = "loading", i.Ready = "ready", i.Playing = "playing", i.Paused = "paused", i.Completed = "completed", i.Error = "error", i.WaitingForInteraction = "waiting_for_interaction", i))(d || {}), u = /* @__PURE__ */ ((i) => (i[i.XML_PARSING_ERROR = 100] = "XML_PARSING_ERROR", i[i.VAST_SCHEMA_VALIDATION_ERROR = 101] = "VAST_SCHEMA_VALIDATION_ERROR", i[i.VAST_VERSION_NOT_SUPPORTED = 102] = "VAST_VERSION_NOT_SUPPORTED", i[i.GENERAL_WRAPPER_ERROR = 300] = "GENERAL_WRAPPER_ERROR", i[i.WRAPPER_TIMEOUT = 301] = "WRAPPER_TIMEOUT", i[i.WRAPPER_LIMIT_REACHED = 302] = "WRAPPER_LIMIT_REACHED", i[i.NO_VAST_RESPONSE = 303] = "NO_VAST_RESPONSE", i[i.GENERAL_LINEAR_ERROR = 400] = "GENERAL_LINEAR_ERROR", i[i.FILE_NOT_FOUND = 401] = "FILE_NOT_FOUND", i[i.MEDIA_TIMEOUT = 402] = "MEDIA_TIMEOUT", i[i.MEDIA_NOT_SUPPORTED = 403] = "MEDIA_NOT_SUPPORTED", i[i.GENERAL_COMPANION_ERROR = 600] = "GENERAL_COMPANION_ERROR", i[i.UNDEFINED_ERROR = 900] = "UNDEFINED_ERROR", i))(u || {});
6
+ class A {
7
+ constructor(e = {}) {
8
+ l(this, "config");
9
+ l(this, "xmlParser");
10
+ this.config = {
11
+ maxWrapperDepth: e.maxWrapperDepth ?? 5,
12
+ timeout: e.timeout ?? 1e4,
13
+ debug: e.debug ?? !1,
14
+ fetchFn: e.fetchFn ?? fetch.bind(globalThis)
15
+ }, this.xmlParser = new T({
16
+ ignoreAttributes: !1,
17
+ attributeNamePrefix: "@_",
18
+ textNodeName: "#text",
19
+ parseAttributeValue: !0,
20
+ trimValues: !0
21
+ });
22
+ }
23
+ /**
24
+ * Parse a VAST URL and resolve all wrappers
25
+ * @param vastUrl - URL to the VAST document
26
+ */
27
+ async parse(e) {
28
+ try {
29
+ return { success: !0, response: await this.fetchAndParse(e, 0) };
30
+ } catch (r) {
31
+ const t = r instanceof Error ? r.message : "Unknown error";
32
+ return {
33
+ success: !1,
34
+ error: {
35
+ code: u.GENERAL_WRAPPER_ERROR,
36
+ message: t
37
+ }
38
+ };
39
+ }
40
+ }
41
+ /**
42
+ * Fetch and parse VAST document with wrapper resolution
43
+ */
44
+ async fetchAndParse(e, r) {
45
+ if (r >= this.config.maxWrapperDepth)
46
+ throw new Error(
47
+ `Wrapper limit exceeded (max: ${this.config.maxWrapperDepth})`
48
+ );
49
+ this.log(`Fetching VAST (depth: ${r}): ${e}`);
50
+ const t = await this.fetchWithTimeout(e), s = this.parseXml(t), a = await Promise.all(
51
+ s.ads.map(async (n) => {
52
+ var h;
53
+ return (h = n.wrapper) != null && h.vastAdTagURI ? this.resolveWrapper(n, r + 1) : n;
54
+ })
55
+ );
56
+ return {
57
+ ...s,
58
+ ads: a.flat()
59
+ };
60
+ }
61
+ /**
62
+ * Resolve a wrapper ad by fetching the nested VAST
63
+ */
64
+ async resolveWrapper(e, r) {
65
+ var t;
66
+ if (!((t = e.wrapper) != null && t.vastAdTagURI))
67
+ return [e];
68
+ try {
69
+ return (await this.fetchAndParse(
70
+ e.wrapper.vastAdTagURI,
71
+ r
72
+ )).ads.map(
73
+ (a) => this.mergeWrapperTracking(e, a)
74
+ );
75
+ } catch (s) {
76
+ if (this.log(`Wrapper resolution failed: ${s}`), e.wrapper.fallbackOnNoAd)
77
+ return [];
78
+ throw s;
79
+ }
80
+ }
81
+ /**
82
+ * Merge tracking events from wrapper into nested ad
83
+ */
84
+ mergeWrapperTracking(e, r) {
85
+ return {
86
+ ...r,
87
+ impressions: [
88
+ ...e.impressions,
89
+ ...r.impressions
90
+ ],
91
+ errors: [...e.errors, ...r.errors],
92
+ creatives: r.creatives.map((t) => ({
93
+ ...t,
94
+ linear: t.linear ? {
95
+ ...t.linear,
96
+ trackingEvents: [
97
+ ...this.getWrapperTrackingEvents(e),
98
+ ...t.linear.trackingEvents
99
+ ]
100
+ } : void 0
101
+ }))
102
+ };
103
+ }
104
+ /**
105
+ * Extract tracking events from wrapper creatives
106
+ */
107
+ getWrapperTrackingEvents(e) {
108
+ var t;
109
+ const r = [];
110
+ for (const s of e.creatives)
111
+ (t = s.linear) != null && t.trackingEvents && r.push(...s.linear.trackingEvents);
112
+ return r;
113
+ }
114
+ /**
115
+ * Fetch URL with timeout
116
+ */
117
+ async fetchWithTimeout(e) {
118
+ const r = new AbortController(), t = setTimeout(
119
+ () => r.abort(),
120
+ this.config.timeout
121
+ );
122
+ try {
123
+ const s = await this.config.fetchFn(e, {
124
+ signal: r.signal
125
+ });
126
+ if (!s.ok)
127
+ throw new Error(`HTTP ${s.status}: ${s.statusText}`);
128
+ return s.text();
129
+ } finally {
130
+ clearTimeout(t);
131
+ }
132
+ }
133
+ /**
134
+ * Parse raw VAST XML into structured response
135
+ */
136
+ parseXml(e) {
137
+ const t = this.xmlParser.parse(e).VAST;
138
+ if (!t)
139
+ throw new Error("Invalid VAST: missing VAST element");
140
+ const s = t["@_version"] || "4.0", a = this.parseAds(t.Ad);
141
+ return {
142
+ version: s,
143
+ ads: a,
144
+ errors: this.parseErrors(t.Error)
145
+ };
146
+ }
147
+ /**
148
+ * Parse Ad elements
149
+ */
150
+ parseAds(e) {
151
+ return e ? (Array.isArray(e) ? e : [e]).map((t) => this.parseAd(t)) : [];
152
+ }
153
+ /**
154
+ * Parse a single Ad element
155
+ */
156
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
157
+ parseAd(e) {
158
+ var s, a, n;
159
+ const r = !!e.Wrapper, t = e.InLine || e.Wrapper;
160
+ return {
161
+ id: e["@_id"] || "",
162
+ sequence: e["@_sequence"],
163
+ adSystem: t != null && t.AdSystem ? {
164
+ name: typeof t.AdSystem == "string" ? t.AdSystem : t.AdSystem["#text"] || "",
165
+ version: (s = t.AdSystem) == null ? void 0 : s["@_version"]
166
+ } : void 0,
167
+ adTitle: t == null ? void 0 : t.AdTitle,
168
+ impressions: this.parseImpressions(t == null ? void 0 : t.Impression),
169
+ errors: this.parseErrors(t == null ? void 0 : t.Error),
170
+ creatives: this.parseCreatives((a = t == null ? void 0 : t.Creatives) == null ? void 0 : a.Creative),
171
+ wrapper: r ? {
172
+ vastAdTagURI: t.VASTAdTagURI,
173
+ followAdditionalWrappers: t["@_followAdditionalWrappers"] !== !1,
174
+ allowMultipleAds: t["@_allowMultipleAds"],
175
+ fallbackOnNoAd: t["@_fallbackOnNoAd"]
176
+ } : void 0,
177
+ inLine: r ? void 0 : {
178
+ adTitle: (t == null ? void 0 : t.AdTitle) || "",
179
+ description: t == null ? void 0 : t.Description,
180
+ advertiser: t == null ? void 0 : t.Advertiser,
181
+ creatives: this.parseCreatives((n = t == null ? void 0 : t.Creatives) == null ? void 0 : n.Creative)
182
+ }
183
+ };
184
+ }
185
+ /**
186
+ * Parse Impression elements
187
+ */
188
+ parseImpressions(e) {
189
+ return e ? (Array.isArray(e) ? e : [e]).map((t) => ({
190
+ id: t["@_id"],
191
+ url: typeof t == "string" ? t : t["#text"] || ""
192
+ })) : [];
193
+ }
194
+ /**
195
+ * Parse Creative elements
196
+ */
197
+ parseCreatives(e) {
198
+ return e ? (Array.isArray(e) ? e : [e]).map((t) => ({
199
+ id: t["@_id"],
200
+ sequence: t["@_sequence"],
201
+ adId: t["@_adId"],
202
+ linear: t.Linear ? this.parseLinear(t.Linear) : void 0
203
+ })) : [];
204
+ }
205
+ /**
206
+ * Parse Linear element
207
+ */
208
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
209
+ parseLinear(e) {
210
+ var r, t;
211
+ return {
212
+ duration: this.parseDuration(e.Duration),
213
+ skipOffset: e["@_skipoffset"] ? this.parseDuration(e["@_skipoffset"]) : void 0,
214
+ mediaFiles: this.parseMediaFiles((r = e.MediaFiles) == null ? void 0 : r.MediaFile),
215
+ trackingEvents: this.parseTrackingEvents(
216
+ (t = e.TrackingEvents) == null ? void 0 : t.Tracking
217
+ ),
218
+ videoClicks: e.VideoClicks ? {
219
+ clickThrough: e.VideoClicks.ClickThrough ? {
220
+ id: e.VideoClicks.ClickThrough["@_id"],
221
+ url: typeof e.VideoClicks.ClickThrough == "string" ? e.VideoClicks.ClickThrough : e.VideoClicks.ClickThrough["#text"] || ""
222
+ } : void 0
223
+ } : void 0,
224
+ adParameters: e.AdParameters
225
+ };
226
+ }
227
+ /**
228
+ * Parse MediaFile elements
229
+ */
230
+ parseMediaFiles(e) {
231
+ return e ? (Array.isArray(e) ? e : [e]).map((t) => ({
232
+ id: t["@_id"],
233
+ url: typeof t == "string" ? t : t["#text"] || "",
234
+ delivery: t["@_delivery"] || "progressive",
235
+ type: t["@_type"] || "video/mp4",
236
+ width: parseInt(t["@_width"], 10) || 0,
237
+ height: parseInt(t["@_height"], 10) || 0,
238
+ bitrate: t["@_bitrate"] ? parseInt(t["@_bitrate"], 10) : void 0,
239
+ codec: t["@_codec"],
240
+ scalable: t["@_scalable"],
241
+ maintainAspectRatio: t["@_maintainAspectRatio"]
242
+ })) : [];
243
+ }
244
+ /**
245
+ * Parse Tracking elements
246
+ */
247
+ parseTrackingEvents(e) {
248
+ return e ? (Array.isArray(e) ? e : [e]).map((t) => ({
249
+ event: t["@_event"],
250
+ url: typeof t == "string" ? t : t["#text"] || "",
251
+ offset: t["@_offset"] ? this.parseDuration(t["@_offset"]) : void 0
252
+ })) : [];
253
+ }
254
+ /**
255
+ * Parse Error elements
256
+ */
257
+ parseErrors(e) {
258
+ return e ? (Array.isArray(e) ? e : [e]).map((t) => typeof t == "string" ? t : t["#text"] || "") : [];
259
+ }
260
+ /**
261
+ * Parse duration string (HH:MM:SS or seconds) to seconds
262
+ */
263
+ parseDuration(e) {
264
+ if (typeof e == "number") return e;
265
+ if (typeof e != "string") return 0;
266
+ if (e.endsWith("%"))
267
+ return parseFloat(e) / 100;
268
+ const r = e.match(/(\d+):(\d+):(\d+(?:\.\d+)?)/);
269
+ if (r) {
270
+ const [, t, s, a] = r;
271
+ return parseInt(t, 10) * 3600 + parseInt(s, 10) * 60 + parseFloat(a);
272
+ }
273
+ return parseFloat(e) || 0;
274
+ }
275
+ /**
276
+ * Select the best media file based on target bitrate
277
+ * Prefers 1080p/720p over 4K for TV compatibility
278
+ */
279
+ selectBestMediaFile(e, r = 2500) {
280
+ if (e.length === 0) return null;
281
+ const t = e.filter(
282
+ (n) => n.type.includes("mp4") || n.type.includes("video/mp4")
283
+ );
284
+ return [...t.length > 0 ? t : e].sort((n, h) => {
285
+ const p = n.bitrate || 0, f = h.bitrate || 0, E = n.height > 1080 ? 1e4 : 0, k = h.height > 1080 ? 1e4 : 0, w = Math.abs(p - r) + E, b = Math.abs(f - r) + k;
286
+ return w - b;
287
+ })[0] || null;
288
+ }
289
+ /**
290
+ * Aggregate all tracking events from parsed ads
291
+ */
292
+ aggregateTrackingEvents(e) {
293
+ var t;
294
+ const r = [];
295
+ for (const s of e)
296
+ for (const a of s.creatives)
297
+ (t = a.linear) != null && t.trackingEvents && r.push(...a.linear.trackingEvents);
298
+ return r;
299
+ }
300
+ /**
301
+ * Get all impression URLs from parsed ads
302
+ */
303
+ aggregateImpressions(e) {
304
+ const r = [];
305
+ for (const t of e)
306
+ for (const s of t.impressions)
307
+ r.push(s.url);
308
+ return r;
309
+ }
310
+ /**
311
+ * Debug logging
312
+ */
313
+ log(e) {
314
+ this.config.debug && console.log(`[VASTParser] ${e}`);
315
+ }
316
+ }
317
+ function m(i, e = {}) {
318
+ let r = i;
319
+ return r = r.replace(/\[TIMESTAMP\]/g, Date.now().toString()), r = r.replace(
320
+ /\[CACHEBUSTING\]/g,
321
+ Math.random().toString(36).substring(2, 15)
322
+ ), e.assetUri && (r = r.replace(
323
+ /\[ASSETURI\]/g,
324
+ encodeURIComponent(e.assetUri)
325
+ )), e.contentPlayhead !== void 0 && (r = r.replace(
326
+ /\[CONTENTPLAYHEAD\]/g,
327
+ v(e.contentPlayhead)
328
+ )), e.adPlayhead !== void 0 && (r = r.replace(
329
+ /\[ADPLAYHEAD\]/g,
330
+ v(e.adPlayhead)
331
+ )), e.errorCode !== void 0 && (r = r.replace(/\[ERRORCODE\]/g, e.errorCode.toString())), e.breakPosition !== void 0 && (r = r.replace(
332
+ /\[BREAKPOSITION\]/g,
333
+ e.breakPosition.toString()
334
+ )), e.adType && (r = r.replace(/\[ADTYPE\]/g, e.adType)), r;
335
+ }
336
+ function v(i) {
337
+ const e = Math.floor(i / 3600), r = Math.floor(i % 3600 / 60), t = Math.floor(i % 60), s = Math.floor(i % 1 * 1e3);
338
+ return e.toString().padStart(2, "0") + ":" + r.toString().padStart(2, "0") + ":" + t.toString().padStart(2, "0") + "." + s.toString().padStart(3, "0");
339
+ }
340
+ var o = /* @__PURE__ */ ((i) => (i.WebOS = "webos", i.Tizen = "tizen", i.Vidaa = "vidaa", i.WhaleOS = "whaleos", i.FireTV = "firetv", i.Roku = "roku", i.Xbox = "xbox", i.PlayStation = "playstation", i.AndroidTV = "androidtv", i.Vizio = "vizio", i.Generic = "generic", i))(o || {}), c = /* @__PURE__ */ ((i) => (i.Enter = "enter", i.Back = "back", i.Left = "left", i.Right = "right", i.Up = "up", i.Down = "down", i.Play = "play", i.Pause = "pause", i.PlayPause = "playPause", i.Stop = "stop", i.FastForward = "fastForward", i.Rewind = "rewind", i.Menu = "menu", i.Info = "info", i.Red = "red", i.Green = "green", i.Yellow = "yellow", i.Blue = "blue", i.ChannelUp = "channelUp", i.ChannelDown = "channelDown", i.VolumeUp = "volumeUp", i.VolumeDown = "volumeDown", i.Mute = "mute", i))(c || {});
341
+ const _ = {
342
+ webos: {
343
+ 13: "enter",
344
+ 461: "back",
345
+ // WebOS specific
346
+ 37: "left",
347
+ 38: "up",
348
+ 39: "right",
349
+ 40: "down",
350
+ 415: "play",
351
+ 19: "pause",
352
+ 413: "stop",
353
+ 417: "fastForward",
354
+ 412: "rewind",
355
+ 457: "info",
356
+ 403: "red",
357
+ 404: "green",
358
+ 405: "yellow",
359
+ 406: "blue",
360
+ 33: "channelUp",
361
+ 34: "channelDown"
362
+ /* ChannelDown */
363
+ },
364
+ tizen: {
365
+ 13: "enter",
366
+ 10009: "back",
367
+ // Tizen specific
368
+ 37: "left",
369
+ 38: "up",
370
+ 39: "right",
371
+ 40: "down",
372
+ 415: "play",
373
+ 19: "pause",
374
+ 10252: "playPause",
375
+ 413: "stop",
376
+ 417: "fastForward",
377
+ 412: "rewind",
378
+ 457: "info",
379
+ 403: "red",
380
+ 404: "green",
381
+ 405: "yellow",
382
+ 406: "blue",
383
+ 427: "channelUp",
384
+ 428: "channelDown",
385
+ 447: "volumeUp",
386
+ 448: "volumeDown",
387
+ 449: "mute"
388
+ /* Mute */
389
+ },
390
+ vidaa: {
391
+ 13: "enter",
392
+ 8: "back",
393
+ 27: "back",
394
+ 37: "left",
395
+ 38: "up",
396
+ 39: "right",
397
+ 40: "down",
398
+ 415: "play",
399
+ 19: "pause",
400
+ 413: "stop",
401
+ 417: "fastForward",
402
+ 412: "rewind"
403
+ /* Rewind */
404
+ },
405
+ whaleos: {
406
+ 13: "enter",
407
+ 27: "back",
408
+ 37: "left",
409
+ 38: "up",
410
+ 39: "right",
411
+ 40: "down",
412
+ 415: "play",
413
+ 19: "pause",
414
+ 413: "stop"
415
+ /* Stop */
416
+ },
417
+ firetv: {
418
+ 13: "enter",
419
+ 4: "back",
420
+ // Android back
421
+ 27: "back",
422
+ 37: "left",
423
+ 38: "up",
424
+ 39: "right",
425
+ 40: "down",
426
+ 85: "playPause",
427
+ 126: "play",
428
+ 127: "pause",
429
+ 89: "rewind",
430
+ 90: "fastForward",
431
+ 82: "menu"
432
+ /* Menu */
433
+ },
434
+ roku: {
435
+ 13: "enter",
436
+ 27: "back",
437
+ 8: "back",
438
+ 37: "left",
439
+ 38: "up",
440
+ 39: "right",
441
+ 40: "down",
442
+ 179: "playPause",
443
+ 178: "stop",
444
+ 228: "fastForward",
445
+ 227: "rewind"
446
+ /* Rewind */
447
+ },
448
+ xbox: {
449
+ 13: "enter",
450
+ // A button
451
+ 27: "back",
452
+ // B button
453
+ 37: "left",
454
+ 38: "up",
455
+ 39: "right",
456
+ 40: "down",
457
+ 195: "menu",
458
+ // Menu button
459
+ 196: "menu"
460
+ /* Menu */
461
+ // View button
462
+ },
463
+ playstation: {
464
+ 13: "enter",
465
+ // X button
466
+ 27: "back",
467
+ // Circle button
468
+ 37: "left",
469
+ 38: "up",
470
+ 39: "right",
471
+ 40: "down"
472
+ /* Down */
473
+ },
474
+ androidtv: {
475
+ 13: "enter",
476
+ 4: "back",
477
+ // Android back
478
+ 27: "back",
479
+ 37: "left",
480
+ 38: "up",
481
+ 39: "right",
482
+ 40: "down",
483
+ 85: "playPause",
484
+ 126: "play",
485
+ 127: "pause",
486
+ 89: "rewind",
487
+ 90: "fastForward",
488
+ 82: "menu"
489
+ /* Menu */
490
+ },
491
+ vizio: {
492
+ 13: "enter",
493
+ 27: "back",
494
+ 8: "back",
495
+ 37: "left",
496
+ 38: "up",
497
+ 39: "right",
498
+ 40: "down",
499
+ 415: "play",
500
+ 19: "pause"
501
+ /* Pause */
502
+ },
503
+ generic: {
504
+ 13: "enter",
505
+ 27: "back",
506
+ // Escape
507
+ 8: "back",
508
+ // Backspace
509
+ 37: "left",
510
+ 38: "up",
511
+ 39: "right",
512
+ 40: "down",
513
+ 32: "playPause",
514
+ // Space
515
+ 80: "play",
516
+ // P key
517
+ 83: "stop",
518
+ // S key
519
+ 77: "mute"
520
+ /* Mute */
521
+ // M key
522
+ }
523
+ }, P = {
524
+ tizen: [
525
+ /Tizen/i,
526
+ /SMART-TV.*Samsung/i
527
+ ],
528
+ webos: [
529
+ /Web0S/i,
530
+ /WebOS/i,
531
+ /LG.*NetCast/i,
532
+ /LGE.*TV/i
533
+ ],
534
+ vidaa: [
535
+ /Vidaa/i,
536
+ /VIDAA/i,
537
+ /Hisense/i
538
+ ],
539
+ whaleos: [
540
+ /WhaleTV/i,
541
+ /Whale/i
542
+ ],
543
+ firetv: [
544
+ /AFT/i,
545
+ // Amazon Fire TV
546
+ /AFTS/i,
547
+ // Fire TV Stick
548
+ /AFTM/i,
549
+ // Fire TV specific models
550
+ /Amazon.*Fire/i
551
+ ],
552
+ roku: [
553
+ /Roku/i
554
+ ],
555
+ xbox: [
556
+ /Xbox/i,
557
+ /Edge.*Xbox/i
558
+ ],
559
+ playstation: [
560
+ /PlayStation/i,
561
+ /PS4/i,
562
+ /PS5/i
563
+ ],
564
+ androidtv: [
565
+ /Android.*TV/i,
566
+ /Chromecast/i,
567
+ /BRAVIA/i,
568
+ // Sony
569
+ /SHIELD/i
570
+ // NVIDIA Shield
571
+ ],
572
+ vizio: [
573
+ /VIZIO/i,
574
+ /SmartCast/i
575
+ ],
576
+ generic: []
577
+ };
578
+ class O {
579
+ constructor() {
580
+ l(this, "platform");
581
+ l(this, "capabilities");
582
+ l(this, "deviceInfo");
583
+ l(this, "keyMap");
584
+ l(this, "reverseKeyMap");
585
+ this.platform = this.detectPlatform(), this.keyMap = _[this.platform], this.reverseKeyMap = this.buildReverseKeyMap(), this.capabilities = this.detectCapabilities(), this.deviceInfo = this.detectDeviceInfo();
586
+ }
587
+ /**
588
+ * Detect the current Smart TV platform using userAgent and global objects
589
+ */
590
+ detectPlatform() {
591
+ if (typeof window > "u" || typeof navigator > "u")
592
+ return o.Generic;
593
+ const e = navigator.userAgent, r = window;
594
+ if (r.tizen)
595
+ return o.Tizen;
596
+ if (r.webOS || r.PalmSystem)
597
+ return o.WebOS;
598
+ for (const [t, s] of Object.entries(P))
599
+ if (t !== o.Generic) {
600
+ for (const a of s)
601
+ if (a.test(e))
602
+ return t;
603
+ }
604
+ return o.Generic;
605
+ }
606
+ /**
607
+ * Detect platform capabilities
608
+ */
609
+ detectCapabilities() {
610
+ const e = typeof navigator < "u", r = typeof document < "u", t = typeof window < "u", s = {
611
+ sendBeacon: e && "sendBeacon" in navigator,
612
+ fetchKeepalive: typeof fetch < "u",
613
+ mutedAutoplayRequired: !0,
614
+ fullscreen: r && ("fullscreenEnabled" in document || "webkitFullscreenEnabled" in document),
615
+ hardwareDecodeInfo: !1,
616
+ hdr: !1,
617
+ hdr10Plus: !1,
618
+ dolbyVision: !1,
619
+ dolbyAtmos: !1,
620
+ hevc: this.isCodecSupported('video/mp4; codecs="hvc1"'),
621
+ vp9: this.isCodecSupported('video/webm; codecs="vp9"'),
622
+ av1: this.isCodecSupported('video/mp4; codecs="av01.0.05M.08"'),
623
+ maxResolution: this.detectMaxResolution(),
624
+ touch: t && "ontouchstart" in window,
625
+ voice: !1
626
+ };
627
+ switch (this.platform) {
628
+ case o.Tizen:
629
+ return {
630
+ ...s,
631
+ hardwareDecodeInfo: !0,
632
+ hdr: !0,
633
+ hevc: !0,
634
+ voice: !0
635
+ };
636
+ case o.WebOS:
637
+ return {
638
+ ...s,
639
+ hardwareDecodeInfo: !0,
640
+ hdr: !0,
641
+ dolbyVision: !0,
642
+ dolbyAtmos: !0,
643
+ hevc: !0,
644
+ voice: !0
645
+ };
646
+ case o.FireTV:
647
+ return {
648
+ ...s,
649
+ hdr: !0,
650
+ hdr10Plus: !0,
651
+ dolbyVision: !0,
652
+ hevc: !0,
653
+ voice: !0
654
+ };
655
+ case o.Roku:
656
+ return {
657
+ ...s,
658
+ hdr: !0,
659
+ dolbyVision: !0,
660
+ hevc: !0,
661
+ voice: !0
662
+ };
663
+ case o.Xbox:
664
+ return {
665
+ ...s,
666
+ hdr: !0,
667
+ dolbyVision: !0,
668
+ dolbyAtmos: !0,
669
+ hevc: !0,
670
+ av1: !0,
671
+ voice: !0
672
+ };
673
+ case o.PlayStation:
674
+ return {
675
+ ...s,
676
+ hdr: !0,
677
+ hevc: !0
678
+ };
679
+ case o.AndroidTV:
680
+ return {
681
+ ...s,
682
+ hdr: !0,
683
+ dolbyVision: !0,
684
+ hevc: !0,
685
+ vp9: !0,
686
+ voice: !0
687
+ };
688
+ default:
689
+ return s;
690
+ }
691
+ }
692
+ /**
693
+ * Detect device information
694
+ */
695
+ detectDeviceInfo() {
696
+ var t, s, a;
697
+ const e = {
698
+ platform: this.platform
699
+ };
700
+ typeof window < "u" && (e.screenWidth = (t = window.screen) == null ? void 0 : t.width, e.screenHeight = (s = window.screen) == null ? void 0 : s.height, e.devicePixelRatio = window.devicePixelRatio);
701
+ const r = window;
702
+ if (this.platform === o.Tizen && ((a = r.tizen) != null && a.systeminfo))
703
+ try {
704
+ r.tizen.systeminfo.getPropertyValue("BUILD", (n) => {
705
+ e.model = n.model, e.manufacturer = n.manufacturer || "Samsung";
706
+ });
707
+ } catch {
708
+ e.manufacturer = "Samsung";
709
+ }
710
+ if (this.platform === o.WebOS && r.webOSSystem)
711
+ try {
712
+ const n = r.webOSSystem.deviceInfo;
713
+ e.model = n == null ? void 0 : n.modelName, e.manufacturer = "LG", e.osVersion = n == null ? void 0 : n.version;
714
+ } catch {
715
+ e.manufacturer = "LG";
716
+ }
717
+ return e;
718
+ }
719
+ /**
720
+ * Detect maximum supported resolution
721
+ */
722
+ detectMaxResolution() {
723
+ var s, a;
724
+ if (typeof window > "u") return "unknown";
725
+ const e = ((s = window.screen) == null ? void 0 : s.width) || 0, r = ((a = window.screen) == null ? void 0 : a.height) || 0, t = Math.max(e, r);
726
+ return t >= 3840 ? "4k" : t >= 1920 ? "1080p" : t >= 1280 ? "720p" : "unknown";
727
+ }
728
+ /**
729
+ * Build reverse mapping from KeyAction to key codes
730
+ */
731
+ buildReverseKeyMap() {
732
+ const e = /* @__PURE__ */ new Map();
733
+ for (const [r, t] of Object.entries(this.keyMap)) {
734
+ const s = parseInt(r, 10), a = e.get(t) || [];
735
+ a.push(s), e.set(t, a);
736
+ }
737
+ return e;
738
+ }
739
+ /**
740
+ * Normalize a raw key code to a KeyAction
741
+ * @param keyCode - Raw keyboard/remote key code
742
+ * @returns KeyAction or null if not mapped
743
+ */
744
+ normalizeKeyCode(e) {
745
+ return this.keyMap[e] ?? null;
746
+ }
747
+ /**
748
+ * Get all key codes that map to a specific action
749
+ * @param action - The key action
750
+ * @returns Array of key codes
751
+ */
752
+ getKeyCodesForAction(e) {
753
+ return this.reverseKeyMap.get(e) || [];
754
+ }
755
+ /**
756
+ * Check if a specific codec is supported
757
+ * @param codec - MIME type with codec string
758
+ * @returns true if codec is supported
759
+ */
760
+ isCodecSupported(e) {
761
+ if (typeof document > "u") return !1;
762
+ try {
763
+ const t = document.createElement("video").canPlayType(e);
764
+ return t === "probably" || t === "maybe";
765
+ } catch {
766
+ return !1;
767
+ }
768
+ }
769
+ /**
770
+ * Register Tizen-specific key handlers
771
+ * Must be called for Tizen apps to receive media keys
772
+ */
773
+ registerTizenKeys() {
774
+ if (this.platform !== o.Tizen) return;
775
+ const e = window.tizen;
776
+ e != null && e.tvinputdevice && [
777
+ "MediaPlay",
778
+ "MediaPause",
779
+ "MediaStop",
780
+ "MediaFastForward",
781
+ "MediaRewind",
782
+ "MediaPlayPause",
783
+ "ColorF0Red",
784
+ "ColorF1Green",
785
+ "ColorF2Yellow",
786
+ "ColorF3Blue",
787
+ "Info"
788
+ ].forEach((t) => {
789
+ try {
790
+ e.tvinputdevice.registerKey(t);
791
+ } catch {
792
+ }
793
+ });
794
+ }
795
+ /**
796
+ * Register WebOS-specific key handlers
797
+ */
798
+ registerWebOSKeys() {
799
+ this.platform, o.WebOS;
800
+ }
801
+ /**
802
+ * Get platform-specific video element attributes
803
+ * @returns Object of attributes to set on video element
804
+ */
805
+ getVideoAttributes() {
806
+ const e = {
807
+ muted: !0,
808
+ playsinline: !0,
809
+ autoplay: !0,
810
+ "webkit-playsinline": !0
811
+ };
812
+ switch (this.platform) {
813
+ case o.Tizen:
814
+ e["data-samsung-immersive"] = "true";
815
+ break;
816
+ case o.WebOS:
817
+ e["data-lg-immersive"] = "true";
818
+ break;
819
+ case o.FireTV:
820
+ case o.AndroidTV:
821
+ e["x-webkit-airplay"] = "allow";
822
+ break;
823
+ }
824
+ return e;
825
+ }
826
+ /**
827
+ * Get recommended video settings for this platform
828
+ */
829
+ getRecommendedVideoSettings() {
830
+ switch (this.platform) {
831
+ case o.Tizen:
832
+ case o.WebOS:
833
+ return {
834
+ maxBitrate: 15e3,
835
+ // 15 Mbps for high-end TVs
836
+ preferredCodec: "hevc",
837
+ maxResolution: "4k"
838
+ };
839
+ case o.FireTV:
840
+ return {
841
+ maxBitrate: 1e4,
842
+ // 10 Mbps
843
+ preferredCodec: "hevc",
844
+ maxResolution: "4k"
845
+ };
846
+ case o.Roku:
847
+ return {
848
+ maxBitrate: 8e3,
849
+ // 8 Mbps
850
+ preferredCodec: "h264",
851
+ // Roku has variable HEVC support
852
+ maxResolution: "4k"
853
+ };
854
+ case o.Xbox:
855
+ case o.PlayStation:
856
+ return {
857
+ maxBitrate: 2e4,
858
+ // 20 Mbps for game consoles
859
+ preferredCodec: "hevc",
860
+ maxResolution: "4k"
861
+ };
862
+ default:
863
+ return {
864
+ maxBitrate: 5e3,
865
+ // 5 Mbps safe default
866
+ preferredCodec: "h264",
867
+ maxResolution: "1080p"
868
+ };
869
+ }
870
+ }
871
+ }
872
+ let g = null;
873
+ function y() {
874
+ return g || (g = new O()), g;
875
+ }
876
+ class x {
877
+ constructor(e = [], r = {}) {
878
+ l(this, "config");
879
+ l(this, "trackingEvents");
880
+ l(this, "firedEvents");
881
+ l(this, "macroContext");
882
+ this.config = {
883
+ debug: r.debug ?? !1,
884
+ retry: r.retry ?? !1,
885
+ maxRetries: r.maxRetries ?? 3
886
+ }, this.trackingEvents = this.groupEventsByType(e), this.firedEvents = /* @__PURE__ */ new Set(), this.macroContext = {};
887
+ }
888
+ /**
889
+ * Group tracking events by their event type
890
+ */
891
+ groupEventsByType(e) {
892
+ const r = /* @__PURE__ */ new Map();
893
+ for (const t of e) {
894
+ const s = r.get(t.event) || [];
895
+ s.push(t), r.set(t.event, s);
896
+ }
897
+ return r;
898
+ }
899
+ /**
900
+ * Update macro context for URL replacement
901
+ */
902
+ updateMacroContext(e) {
903
+ this.macroContext = { ...this.macroContext, ...e };
904
+ }
905
+ /**
906
+ * Track a specific event type
907
+ * @param eventType - The VAST event type to track
908
+ * @param once - Only fire once per event type (default: true)
909
+ */
910
+ track(e, r = !0) {
911
+ const t = this.trackingEvents.get(e);
912
+ if (!t) {
913
+ this.log(`No tracking URLs for event: ${e}`);
914
+ return;
915
+ }
916
+ for (const s of t) {
917
+ const a = `${e}:${s.url}`;
918
+ if (r && this.firedEvents.has(a)) {
919
+ this.log(`Skipping duplicate event: ${e}`);
920
+ continue;
921
+ }
922
+ const n = m(s.url, this.macroContext);
923
+ this.firePixel(n), r && this.firedEvents.add(a);
924
+ }
925
+ }
926
+ /**
927
+ * Fire a single tracking pixel (fire-and-forget)
928
+ * Uses sendBeacon when available, falls back to fetch with keepalive
929
+ */
930
+ firePixel(e) {
931
+ const r = y();
932
+ this.log(`Firing pixel: ${e}`);
933
+ try {
934
+ if (r.capabilities.sendBeacon && navigator.sendBeacon(e))
935
+ return;
936
+ if (r.capabilities.fetchKeepalive) {
937
+ fetch(e, {
938
+ method: "GET",
939
+ keepalive: !0,
940
+ mode: "no-cors",
941
+ credentials: "omit"
942
+ }).catch(() => {
943
+ });
944
+ return;
945
+ }
946
+ this.fireImageBeacon(e);
947
+ } catch {
948
+ this.log(`Failed to fire pixel: ${e}`);
949
+ }
950
+ }
951
+ /**
952
+ * Image beacon fallback
953
+ */
954
+ fireImageBeacon(e) {
955
+ const r = new Image(1, 1);
956
+ r.src = e;
957
+ }
958
+ /**
959
+ * Fire impression pixels
960
+ * @param impressionUrls - Array of impression URLs
961
+ */
962
+ fireImpressions(e) {
963
+ for (const r of e) {
964
+ const t = m(r, this.macroContext);
965
+ this.firePixel(t);
966
+ }
967
+ }
968
+ /**
969
+ * Fire error tracking with error code
970
+ * @param errorUrls - Array of error tracking URLs
971
+ * @param errorCode - VAST error code
972
+ */
973
+ fireError(e, r) {
974
+ const t = { ...this.macroContext, errorCode: r };
975
+ for (const s of e) {
976
+ const a = m(s, t);
977
+ this.firePixel(a);
978
+ }
979
+ }
980
+ /**
981
+ * Reset fired events (for replay scenarios)
982
+ */
983
+ reset() {
984
+ this.firedEvents.clear();
985
+ }
986
+ /**
987
+ * Debug logging
988
+ */
989
+ log(e) {
990
+ this.config.debug && console.log(`[AdTracker] ${e}`);
991
+ }
992
+ }
993
+ const I = {
994
+ targetBitrate: 2500,
995
+ maxWrapperDepth: 5,
996
+ timeout: 1e4,
997
+ debug: !1,
998
+ skipButtonText: "Skip Ad"
999
+ };
1000
+ class N {
1001
+ constructor(e) {
1002
+ l(this, "config");
1003
+ l(this, "platform");
1004
+ l(this, "parser");
1005
+ l(this, "videoElement", null);
1006
+ l(this, "overlayElement", null);
1007
+ l(this, "skipButtonElement", null);
1008
+ l(this, "tracker", null);
1009
+ l(this, "state");
1010
+ l(this, "ads", []);
1011
+ l(this, "listeners", /* @__PURE__ */ new Set());
1012
+ l(this, "quartilesFired", /* @__PURE__ */ new Set());
1013
+ l(this, "boundKeyHandler", null);
1014
+ l(this, "focusTrap", null);
1015
+ if (this.config = { ...I, ...e }, !this.config.container)
1016
+ throw new Error("Container element is required");
1017
+ this.platform = y(), this.parser = new A({
1018
+ maxWrapperDepth: this.config.maxWrapperDepth,
1019
+ timeout: this.config.timeout,
1020
+ debug: this.config.debug
1021
+ }), this.state = {
1022
+ status: d.Idle,
1023
+ currentTime: 0,
1024
+ duration: 0,
1025
+ muted: !0,
1026
+ volume: 1,
1027
+ canSkip: !1,
1028
+ skipCountdown: 0,
1029
+ mediaFile: null,
1030
+ error: null
1031
+ }, this.platform.platform === "tizen" && this.platform.registerTizenKeys();
1032
+ }
1033
+ /**
1034
+ * Initialize the SDK: fetch VAST, create video element, attempt autoplay
1035
+ */
1036
+ async init() {
1037
+ var e, r;
1038
+ if (!this.config.container)
1039
+ throw new Error("Container element not found");
1040
+ this.updateState({ status: d.Loading });
1041
+ try {
1042
+ const t = await this.parser.parse(this.config.vastUrl);
1043
+ if (!t.success || !t.response)
1044
+ throw this.createError(
1045
+ ((e = t.error) == null ? void 0 : e.code) || u.NO_VAST_RESPONSE,
1046
+ ((r = t.error) == null ? void 0 : r.message) || "Failed to parse VAST"
1047
+ );
1048
+ if (this.ads = t.response.ads, this.ads.length === 0)
1049
+ throw this.createError(
1050
+ u.NO_VAST_RESPONSE,
1051
+ "No ads in VAST response"
1052
+ );
1053
+ const s = this.getFirstLinearCreative();
1054
+ if (!s)
1055
+ throw this.createError(
1056
+ u.GENERAL_LINEAR_ERROR,
1057
+ "No linear creative found"
1058
+ );
1059
+ const a = this.parser.selectBestMediaFile(
1060
+ s.mediaFiles,
1061
+ this.config.targetBitrate
1062
+ );
1063
+ if (!a)
1064
+ throw this.createError(
1065
+ u.FILE_NOT_FOUND,
1066
+ "No suitable media file found"
1067
+ );
1068
+ this.updateState({ mediaFile: a });
1069
+ const n = this.parser.aggregateTrackingEvents(this.ads);
1070
+ this.tracker = new x(n, { debug: this.config.debug }), this.tracker.updateMacroContext({ assetUri: a.url }), this.createVideoElement(a), this.setupFocusManagement(), await this.attemptAutoplay();
1071
+ } catch (t) {
1072
+ const s = t instanceof Error ? this.createError(u.UNDEFINED_ERROR, t.message) : t;
1073
+ this.handleError(s);
1074
+ }
1075
+ }
1076
+ /**
1077
+ * Get the first linear creative from parsed ads
1078
+ */
1079
+ getFirstLinearCreative() {
1080
+ for (const e of this.ads)
1081
+ for (const r of e.creatives)
1082
+ if (r.linear)
1083
+ return r.linear;
1084
+ return null;
1085
+ }
1086
+ /**
1087
+ * Create video element with "Nuclear Mute" strategy
1088
+ * Applies muted, playsinline, autoplay for maximum TV compatibility
1089
+ */
1090
+ createVideoElement(e) {
1091
+ const r = document.createElement("video"), t = this.platform.getVideoAttributes();
1092
+ Object.entries(t).forEach(([s, a]) => {
1093
+ typeof a == "boolean" ? a && r.setAttribute(s, "") : r.setAttribute(s, a);
1094
+ }), r.src = e.url, r.style.cssText = `
1095
+ width: 100%;
1096
+ height: 100%;
1097
+ object-fit: contain;
1098
+ background: #000;
1099
+ `, r.addEventListener("loadedmetadata", () => {
1100
+ this.updateState({
1101
+ duration: r.duration,
1102
+ status: d.Ready
1103
+ }), this.emit({ type: "loaded" });
1104
+ }), r.addEventListener("timeupdate", () => {
1105
+ this.handleTimeUpdate(r);
1106
+ }), r.addEventListener("ended", () => {
1107
+ this.handleComplete();
1108
+ }), r.addEventListener("error", () => {
1109
+ const s = r.error;
1110
+ this.handleError(
1111
+ this.createError(
1112
+ u.MEDIA_NOT_SUPPORTED,
1113
+ (s == null ? void 0 : s.message) || "Video playback error"
1114
+ )
1115
+ );
1116
+ }), r.addEventListener("play", () => {
1117
+ this.updateState({ status: d.Playing });
1118
+ }), r.addEventListener("pause", () => {
1119
+ var s;
1120
+ this.state.status !== d.Completed && (this.updateState({ status: d.Paused }), this.emit({ type: "pause" }), (s = this.tracker) == null || s.track("pause"));
1121
+ }), this.config.container.appendChild(r), this.videoElement = r, this.log(`Video element created with src: ${e.url}`);
1122
+ }
1123
+ /**
1124
+ * Attempt autoplay with soft-fail handling
1125
+ * If play() rejects (common on TVs), show interactive overlay
1126
+ */
1127
+ async attemptAutoplay() {
1128
+ if (this.videoElement)
1129
+ try {
1130
+ await this.videoElement.play(), this.handlePlaybackStart();
1131
+ } catch (e) {
1132
+ this.log(`Autoplay failed: ${e}`), this.showStartOverlay();
1133
+ }
1134
+ }
1135
+ /**
1136
+ * Show interactive overlay for manual ad start (autoplay fallback)
1137
+ */
1138
+ showStartOverlay() {
1139
+ if (this.updateState({ status: d.WaitingForInteraction }), this.config.customStartOverlay) {
1140
+ this.overlayElement = this.config.customStartOverlay, this.config.container.appendChild(this.overlayElement);
1141
+ return;
1142
+ }
1143
+ const e = document.createElement("div");
1144
+ e.innerHTML = `
1145
+ <div style="
1146
+ position: absolute;
1147
+ top: 0;
1148
+ left: 0;
1149
+ width: 100%;
1150
+ height: 100%;
1151
+ display: flex;
1152
+ align-items: center;
1153
+ justify-content: center;
1154
+ background: rgba(0, 0, 0, 0.7);
1155
+ z-index: 100;
1156
+ ">
1157
+ <button id="adgent-start-btn" style="
1158
+ padding: 20px 40px;
1159
+ font-size: 24px;
1160
+ background: #fff;
1161
+ color: #000;
1162
+ border: none;
1163
+ border-radius: 8px;
1164
+ cursor: pointer;
1165
+ font-weight: bold;
1166
+ ">
1167
+ ▶ Start Ad
1168
+ </button>
1169
+ </div>
1170
+ `, e.style.cssText = "position: absolute; top: 0; left: 0; width: 100%; height: 100%;";
1171
+ const r = e.querySelector("#adgent-start-btn");
1172
+ r == null || r.addEventListener("click", () => this.onStartClick()), this.config.container.style.position = "relative", this.config.container.appendChild(e), this.overlayElement = e, r == null || r.focus();
1173
+ }
1174
+ /**
1175
+ * Handle start button click (from overlay)
1176
+ */
1177
+ async onStartClick() {
1178
+ if (this.removeOverlay(), this.videoElement)
1179
+ try {
1180
+ await this.videoElement.play(), this.handlePlaybackStart();
1181
+ } catch (e) {
1182
+ this.handleError(
1183
+ this.createError(
1184
+ u.GENERAL_LINEAR_ERROR,
1185
+ `Playback failed: ${e}`
1186
+ )
1187
+ );
1188
+ }
1189
+ }
1190
+ /**
1191
+ * Remove the start overlay
1192
+ */
1193
+ removeOverlay() {
1194
+ this.overlayElement && (this.overlayElement.remove(), this.overlayElement = null);
1195
+ }
1196
+ /**
1197
+ * Handle successful playback start
1198
+ */
1199
+ handlePlaybackStart() {
1200
+ var r, t, s, a, n;
1201
+ this.updateState({ status: d.Playing }), this.emit({ type: "start" }), (t = (r = this.config).onStart) == null || t.call(r);
1202
+ const e = this.parser.aggregateImpressions(this.ads);
1203
+ (s = this.tracker) == null || s.fireImpressions(e), (a = this.tracker) == null || a.track("start"), (n = this.tracker) == null || n.track("creativeView"), this.setupSkipButton(), this.log("Playback started");
1204
+ }
1205
+ /**
1206
+ * Handle time updates for progress tracking
1207
+ */
1208
+ handleTimeUpdate(e) {
1209
+ var h, p, f;
1210
+ const r = e.currentTime, t = e.duration;
1211
+ if (!t || isNaN(t)) return;
1212
+ const s = r / t * 100, a = this.calculateQuartile(s);
1213
+ this.updateState({
1214
+ currentTime: r,
1215
+ duration: t
1216
+ }), this.updateSkipCountdown(r), (h = this.tracker) == null || h.updateMacroContext({ adPlayhead: r });
1217
+ const n = {
1218
+ currentTime: r,
1219
+ duration: t,
1220
+ percentage: s,
1221
+ quartile: a
1222
+ };
1223
+ this.emit({ type: "progress", data: n }), (f = (p = this.config).onProgress) == null || f.call(p, n), this.fireQuartileEvents(s);
1224
+ }
1225
+ /**
1226
+ * Calculate current quartile (0-4)
1227
+ */
1228
+ calculateQuartile(e) {
1229
+ return e >= 100 ? 4 : e >= 75 ? 3 : e >= 50 ? 2 : e >= 25 ? 1 : 0;
1230
+ }
1231
+ /**
1232
+ * Fire quartile tracking events
1233
+ */
1234
+ fireQuartileEvents(e) {
1235
+ var t;
1236
+ const r = [
1237
+ { threshold: 25, event: "firstQuartile" },
1238
+ { threshold: 50, event: "midpoint" },
1239
+ { threshold: 75, event: "thirdQuartile" }
1240
+ ];
1241
+ for (const { threshold: s, event: a } of r)
1242
+ e >= s && !this.quartilesFired.has(s) && (this.quartilesFired.add(s), (t = this.tracker) == null || t.track(a), this.emit({ type: "quartile", data: { quartile: a } }), this.log(`Quartile fired: ${a}`));
1243
+ }
1244
+ /**
1245
+ * Set up skip button
1246
+ */
1247
+ setupSkipButton() {
1248
+ const e = this.getFirstLinearCreative(), r = this.config.skipOffset ?? (e == null ? void 0 : e.skipOffset);
1249
+ if (!r || r <= 0)
1250
+ return;
1251
+ this.updateState({ skipCountdown: r, canSkip: !1 });
1252
+ const t = document.createElement("button");
1253
+ t.id = "adgent-skip-btn", t.style.cssText = `
1254
+ position: absolute;
1255
+ bottom: 20px;
1256
+ right: 20px;
1257
+ padding: 12px 24px;
1258
+ font-size: 16px;
1259
+ background: rgba(0, 0, 0, 0.7);
1260
+ color: #fff;
1261
+ border: 2px solid #fff;
1262
+ border-radius: 4px;
1263
+ cursor: pointer;
1264
+ z-index: 101;
1265
+ transition: opacity 0.3s;
1266
+ `, t.textContent = `Skip in ${r}s`, t.addEventListener("click", () => this.skip()), this.config.container.appendChild(t), this.skipButtonElement = t;
1267
+ }
1268
+ /**
1269
+ * Update skip countdown
1270
+ */
1271
+ updateSkipCountdown(e) {
1272
+ const r = this.getFirstLinearCreative(), t = this.config.skipOffset ?? (r == null ? void 0 : r.skipOffset);
1273
+ if (!t || !this.skipButtonElement) return;
1274
+ const s = Math.max(0, t - e);
1275
+ this.updateState({ skipCountdown: s }), s <= 0 && !this.state.canSkip ? (this.updateState({ canSkip: !0 }), this.skipButtonElement.textContent = this.config.skipButtonText, this.skipButtonElement.style.opacity = "1") : s > 0 && (this.skipButtonElement.textContent = `Skip in ${Math.ceil(s)}s`, this.skipButtonElement.style.opacity = "0.6");
1276
+ }
1277
+ /**
1278
+ * Skip the ad
1279
+ */
1280
+ skip() {
1281
+ var e, r, t;
1282
+ if (!this.state.canSkip) {
1283
+ this.log("Skip not available yet");
1284
+ return;
1285
+ }
1286
+ (e = this.tracker) == null || e.track("skip"), this.emit({ type: "skip" }), (t = (r = this.config).onSkip) == null || t.call(r), this.destroy(), this.log("Ad skipped");
1287
+ }
1288
+ /**
1289
+ * Handle ad completion
1290
+ */
1291
+ handleComplete() {
1292
+ var e, r, t;
1293
+ this.updateState({ status: d.Completed }), (e = this.tracker) == null || e.track("complete"), this.emit({ type: "complete" }), (t = (r = this.config).onComplete) == null || t.call(r), this.log("Ad completed");
1294
+ }
1295
+ /**
1296
+ * Handle errors with recovery attempt or callback
1297
+ */
1298
+ handleError(e) {
1299
+ var t, s, a;
1300
+ this.updateState({
1301
+ status: d.Error,
1302
+ error: e
1303
+ });
1304
+ const r = [];
1305
+ for (const n of this.ads)
1306
+ r.push(...n.errors);
1307
+ (t = this.tracker) == null || t.fireError(r, e.code), this.emit({ type: "error", data: e }), (a = (s = this.config).onError) == null || a.call(s, e), this.log(`Error: ${e.message}`);
1308
+ }
1309
+ /**
1310
+ * Set up focus management to capture remote keys
1311
+ */
1312
+ setupFocusManagement() {
1313
+ this.focusTrap = document.createElement("div"), this.focusTrap.tabIndex = 0, this.focusTrap.style.cssText = "position: absolute; opacity: 0; width: 0; height: 0;", this.config.container.appendChild(this.focusTrap), this.focusTrap.focus(), this.boundKeyHandler = (e) => {
1314
+ const r = this.platform.normalizeKeyCode(e.keyCode);
1315
+ r && (e.preventDefault(), e.stopPropagation(), this.handleKeyAction(r));
1316
+ }, document.addEventListener("keydown", this.boundKeyHandler, !0);
1317
+ }
1318
+ /**
1319
+ * Handle key actions from remote control
1320
+ */
1321
+ handleKeyAction(e) {
1322
+ var r, t;
1323
+ switch (this.log(`Key action: ${e}`), e) {
1324
+ case c.Enter:
1325
+ this.state.status === d.WaitingForInteraction ? this.onStartClick() : this.state.canSkip && this.skip();
1326
+ break;
1327
+ case c.Back:
1328
+ this.log("Back pressed - ignoring during ad");
1329
+ break;
1330
+ case c.Play:
1331
+ (r = this.videoElement) == null || r.play();
1332
+ break;
1333
+ case c.Pause:
1334
+ (t = this.videoElement) == null || t.pause();
1335
+ break;
1336
+ case c.Left:
1337
+ case c.Right:
1338
+ case c.Up:
1339
+ case c.Down:
1340
+ break;
1341
+ }
1342
+ }
1343
+ /**
1344
+ * Unmute video (call after playback starts if needed)
1345
+ */
1346
+ unmute() {
1347
+ var e;
1348
+ this.videoElement && (this.videoElement.muted = !1, this.updateState({ muted: !1 }), (e = this.tracker) == null || e.track("unmute"), this.emit({ type: "unmute" }));
1349
+ }
1350
+ /**
1351
+ * Mute video
1352
+ */
1353
+ mute() {
1354
+ var e;
1355
+ this.videoElement && (this.videoElement.muted = !0, this.updateState({ muted: !0 }), (e = this.tracker) == null || e.track("mute"), this.emit({ type: "mute" }));
1356
+ }
1357
+ /**
1358
+ * Add event listener
1359
+ */
1360
+ on(e) {
1361
+ return this.listeners.add(e), () => this.listeners.delete(e);
1362
+ }
1363
+ /**
1364
+ * Get current state
1365
+ */
1366
+ getState() {
1367
+ return { ...this.state };
1368
+ }
1369
+ /**
1370
+ * Clean up all resources
1371
+ */
1372
+ destroy() {
1373
+ var e, r, t, s, a;
1374
+ this.boundKeyHandler && (document.removeEventListener("keydown", this.boundKeyHandler, !0), this.boundKeyHandler = null), (e = this.videoElement) == null || e.remove(), (r = this.overlayElement) == null || r.remove(), (t = this.skipButtonElement) == null || t.remove(), (s = this.focusTrap) == null || s.remove(), this.videoElement = null, this.overlayElement = null, this.skipButtonElement = null, this.focusTrap = null, (a = this.tracker) == null || a.reset(), this.quartilesFired.clear(), this.listeners.clear(), this.ads = [], this.emit({ type: "destroy" }), this.log("Player destroyed");
1375
+ }
1376
+ /**
1377
+ * Update internal state
1378
+ */
1379
+ updateState(e) {
1380
+ this.state = { ...this.state, ...e };
1381
+ }
1382
+ /**
1383
+ * Emit event to listeners
1384
+ */
1385
+ emit(e) {
1386
+ for (const r of this.listeners)
1387
+ try {
1388
+ r(e);
1389
+ } catch (t) {
1390
+ this.log(`Listener error: ${t}`);
1391
+ }
1392
+ }
1393
+ /**
1394
+ * Create error object
1395
+ */
1396
+ createError(e, r, t = !1) {
1397
+ return { code: e, message: r, recoverable: t };
1398
+ }
1399
+ /**
1400
+ * Debug logging
1401
+ */
1402
+ log(e) {
1403
+ this.config.debug && console.log(`[AdPlayer] ${e}`);
1404
+ }
1405
+ }
1406
+ export {
1407
+ N as AdPlayer,
1408
+ x as AdTracker,
1409
+ N as AdgentSDK,
1410
+ _ as DEFAULT_KEY_CODES,
1411
+ c as KeyAction,
1412
+ P as PLATFORM_DETECTION_PATTERNS,
1413
+ o as Platform,
1414
+ O as PlatformAdapter,
1415
+ d as PlaybackStatus,
1416
+ u as VASTErrorCode,
1417
+ A as VASTParser,
1418
+ y as getPlatformAdapter,
1419
+ m as replaceMacros
1420
+ };