@guardvideo/player-sdk 1.1.0 → 1.3.0

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.
@@ -37281,33 +37281,878 @@ Schedule: ${scheduleItems.map(seg => segmentToString(seg))} pos: ${this.timeline
37281
37281
  }
37282
37282
  };
37283
37283
 
37284
- class GuardVideoPlayer extends GuardVideoPlayer$1 {
37285
- static create(containerId, videoId, config) {
37286
- const container = document.getElementById(containerId);
37287
- if (!container) {
37288
- throw new Error(`Container element with id "${containerId}" not found`);
37289
- }
37290
- const videoElement = document.createElement('video');
37291
- videoElement.setAttribute('playsinline', '');
37292
- videoElement.setAttribute('preload', 'metadata');
37293
- videoElement.style.width = '100%';
37294
- videoElement.style.height = '100%';
37295
- videoElement.style.objectFit = 'contain';
37284
+ function formatTime(s) {
37285
+ if (!isFinite(s) || isNaN(s))
37286
+ return '0:00';
37287
+ const h = Math.floor(s / 3600);
37288
+ const m = Math.floor((s % 3600) / 60);
37289
+ const sc = Math.floor(s % 60);
37290
+ const mm = String(m).padStart(h > 0 ? 2 : 1, '0');
37291
+ const ss = String(sc).padStart(2, '0');
37292
+ return h > 0 ? `${h}:${mm}:${ss}` : `${mm}:${ss}`;
37293
+ }
37294
+ function el(tag, cls, attrs) {
37295
+ const e = document.createElement(tag);
37296
+ if (cls)
37297
+ e.className = cls;
37298
+ if (attrs)
37299
+ for (const [k, v] of Object.entries(attrs))
37300
+ e.setAttribute(k, v);
37301
+ return e;
37302
+ }
37303
+ function svgEl(path, w = 20, h = 20) {
37304
+ const s = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
37305
+ s.setAttribute('width', String(w));
37306
+ s.setAttribute('height', String(h));
37307
+ s.setAttribute('viewBox', '0 0 24 24');
37308
+ s.setAttribute('fill', 'currentColor');
37309
+ s.setAttribute('aria-hidden', 'true');
37310
+ s.innerHTML = path;
37311
+ return s;
37312
+ }
37313
+ const ICON = {
37314
+ play: '<polygon points="5,3 19,12 5,21"/>',
37315
+ pause: '<rect x="6" y="4" width="4" height="16" rx="1"/><rect x="14" y="4" width="4" height="16" rx="1"/>',
37316
+ replay: '<path d="M12 5V1L7 6l5 5V7c3.31 0 6 2.69 6 6s-2.69 6-6 6-6-2.69-6-6H4c0 4.42 3.58 8 8 8s8-3.58 8-8-3.58-8-8-8z"/>',
37317
+ volHigh: '<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"/>',
37318
+ volMute: '<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"/>',
37319
+ fsEnter: '<path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"/>',
37320
+ fsExit: '<path d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"/>',
37321
+ settings: '<path d="M19.14,12.94c0.04-0.3,0.06-0.61,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.7-1.62-0.94L14.4,2.81c-0.04-0.24-0.24-0.41-0.48-0.41h-3.84c-0.24,0-0.43,0.17-0.47,0.41L9.25,5.35C8.66,5.59,8.12,5.92,7.63,6.29L5.24,5.33c-0.22-0.08-0.47,0-0.59,0.22L2.74,8.87C2.62,9.08,2.66,9.34,2.86,9.48l2.03,1.58C4.84,11.36,4.8,11.69,4.8,12s0.02,0.64,0.07,0.94l-2.03,1.58c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-.96c0.5,0.38,1.03,0.7,1.62,0.94l0.36,2.54c0.05,0.24,0.24,0.41,0.48,0.41h3.84c0.24,0,0.44-0.17,0.47-0.41l0.36-2.54c0.59-0.24,1.13-0.56,1.62-0.94l2.39,0.96c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.12-0.22,0.07-0.47-0.12-0.61L19.14,12.94zM12,15.6c-1.98,0-3.6-1.62-3.6-3.6s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z"/>',
37322
+ shield: '<path fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>',
37323
+ };
37324
+ const SPEEDS = [0.5, 0.75, 1, 1.25, 1.5, 2];
37325
+ let _stylesInjected = false;
37326
+ function injectStyles() {
37327
+ if (_stylesInjected)
37328
+ return;
37329
+ _stylesInjected = true;
37330
+ const css = `
37331
+ /* ── GuardVideo Player UI ─────────────────────────────────── */
37332
+ .gvp-root {
37333
+ --gvp-accent: #44c09b;
37334
+ position: relative;
37335
+ background: #000;
37336
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
37337
+ border-radius: 10px;
37338
+ overflow: hidden;
37339
+ -webkit-user-select: none;
37340
+ user-select: none;
37341
+ outline: none;
37342
+ }
37343
+ .gvp-root:focus-visible { outline: 2px solid var(--gvp-accent); outline-offset: 1px; }
37344
+
37345
+ .gvp-video { display: block; width: 100%; height: 100%; object-fit: contain; }
37346
+
37347
+ /* ── Utility: hide elements ────────────────────────────────── */
37348
+ .gvp-hidden { display: none !important; }
37349
+ /* Controls use opacity/translate so they animate out gracefully */
37350
+ .gvp-controls-hidden { opacity: 0; transform: translateY(6px); pointer-events: none; }
37351
+
37352
+ /* ── Spinner ─────────────────────────────────────────────── */
37353
+ .gvp-spinner {
37354
+ position: absolute; inset: 0; display: flex;
37355
+ align-items: center; justify-content: center;
37356
+ pointer-events: none; background: rgba(0,0,0,.55); border-radius: 10px;
37357
+ }
37358
+ .gvp-spinner-ring {
37359
+ width: 48px; height: 48px;
37360
+ border: 3px solid rgba(255,255,255,.15);
37361
+ border-top-color: var(--gvp-accent);
37362
+ border-radius: 50%; animation: gvp-spin .8s linear infinite;
37363
+ }
37364
+ @keyframes gvp-spin { to { transform: rotate(360deg); } }
37365
+
37366
+ /* ── Centre play button ────────────────────────────────────── */
37367
+ .gvp-center-play {
37368
+ position: absolute; inset: 0; z-index: 3;
37369
+ display: flex; align-items: center; justify-content: center;
37370
+ cursor: pointer; background: rgba(0,0,0,.3);
37371
+ transition: background .2s; border-radius: 10px;
37372
+ }
37373
+ .gvp-center-play:hover { background: rgba(0,0,0,.45); }
37374
+ .gvp-center-play-btn {
37375
+ width: 72px; height: 72px;
37376
+ background: rgba(255,255,255,.12); backdrop-filter: blur(8px);
37377
+ border-radius: 50%; display: flex; align-items: center; justify-content: center;
37378
+ border: 2px solid rgba(255,255,255,.25);
37379
+ transition: background .2s, transform .15s; color: #fff;
37380
+ }
37381
+ .gvp-center-play:hover .gvp-center-play-btn { background: var(--gvp-accent); transform: scale(1.08); }
37382
+
37383
+ /* ── Click-to-toggle overlay ───────────────────────────────── */
37384
+ .gvp-click-area { position: absolute; inset: 0; cursor: pointer; z-index: 1; }
37385
+
37386
+ /* ── Ripple ───────────────────────────────────────────────── */
37387
+ .gvp-ripple {
37388
+ position: absolute; border-radius: 50%; transform: scale(0);
37389
+ background: rgba(255,255,255,.25);
37390
+ animation: gvp-ripple-anim .5s ease-out forwards;
37391
+ pointer-events: none; z-index: 2;
37392
+ }
37393
+ @keyframes gvp-ripple-anim { to { transform: scale(4); opacity: 0; } }
37394
+
37395
+ /* ── Controls bar ──────────────────────────────────────────── */
37396
+ .gvp-controls {
37397
+ position: absolute; bottom: 0; left: 0; right: 0; z-index: 10;
37398
+ background: linear-gradient(to top, rgba(0,0,0,.85) 0%, transparent 100%);
37399
+ padding: 36px 14px 10px;
37400
+ transition: opacity .3s, transform .3s;
37401
+ border-radius: 0 0 10px 10px;
37402
+ }
37403
+
37404
+ /* ── Seek bar ──────────────────────────────────────────────── */
37405
+ .gvp-seek-row { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
37406
+ .gvp-seek-wrap {
37407
+ flex: 1; position: relative; height: 4px; cursor: pointer;
37408
+ padding: 8px 0; margin: -8px 0; box-sizing: content-box;
37409
+ /* Prevent page scroll while touch-seeking */
37410
+ touch-action: none;
37411
+ }
37412
+ .gvp-seek-track {
37413
+ height: 4px; background: rgba(255,255,255,.2); border-radius: 4px;
37414
+ position: relative; overflow: hidden; transition: height .15s;
37415
+ pointer-events: none;
37416
+ }
37417
+ .gvp-seek-wrap:hover .gvp-seek-track,
37418
+ .gvp-seek-wrap.gvp-dragging .gvp-seek-track { height: 6px; }
37419
+
37420
+ .gvp-seek-buffered {
37421
+ position: absolute; left: 0; top: 0; height: 100%;
37422
+ background: rgba(255,255,255,.35); border-radius: 4px; pointer-events: none;
37423
+ }
37424
+ .gvp-seek-progress {
37425
+ position: absolute; left: 0; top: 0; height: 100%;
37426
+ background: var(--gvp-accent); border-radius: 4px; pointer-events: none;
37427
+ }
37428
+ .gvp-seek-thumb {
37429
+ position: absolute; top: 50%; transform: translate(-50%,-50%);
37430
+ width: 13px; height: 13px; background: #fff; border-radius: 50%;
37431
+ box-shadow: 0 1px 4px rgba(0,0,0,.5); pointer-events: none;
37432
+ opacity: 0; transition: opacity .15s;
37433
+ }
37434
+ .gvp-seek-wrap:hover .gvp-seek-thumb,
37435
+ .gvp-seek-wrap.gvp-dragging .gvp-seek-thumb { opacity: 1; }
37436
+
37437
+ .gvp-seek-tooltip {
37438
+ position: absolute; bottom: 24px; transform: translateX(-50%);
37439
+ background: rgba(0,0,0,.8); color: #fff; font-size: 11px; font-weight: 500;
37440
+ padding: 3px 7px; border-radius: 4px;
37441
+ pointer-events: none; white-space: nowrap;
37442
+ opacity: 0; transition: opacity .1s;
37443
+ }
37444
+ .gvp-seek-wrap:hover .gvp-seek-tooltip { opacity: 1; }
37445
+
37446
+ /* ── Button row ────────────────────────────────────────────── */
37447
+ .gvp-btn-row { display: flex; align-items: center; gap: 4px; }
37448
+ .gvp-btn {
37449
+ background: none; border: none; color: #fff; cursor: pointer;
37450
+ padding: 5px; border-radius: 6px;
37451
+ display: flex; align-items: center; justify-content: center;
37452
+ transition: background .15s, color .15s; flex-shrink: 0;
37453
+ }
37454
+ .gvp-btn:hover { background: rgba(255,255,255,.12); color: var(--gvp-accent); }
37455
+ .gvp-btn:active { background: rgba(255,255,255,.2); }
37456
+
37457
+ /* ── Volume ────────────────────────────────────────────────── */
37458
+ .gvp-volume-wrap { display: flex; align-items: center; gap: 5px; }
37459
+ .gvp-volume-slider {
37460
+ -webkit-appearance: none; width: 70px; height: 4px;
37461
+ background: rgba(255,255,255,.25); border-radius: 4px;
37462
+ outline: none; cursor: pointer; accent-color: var(--gvp-accent);
37463
+ }
37464
+ .gvp-volume-slider::-webkit-slider-thumb {
37465
+ -webkit-appearance: none; width: 12px; height: 12px;
37466
+ border-radius: 50%; background: #fff; cursor: pointer;
37467
+ box-shadow: 0 1px 3px rgba(0,0,0,.4);
37468
+ }
37469
+ .gvp-volume-slider::-moz-range-thumb {
37470
+ width: 12px; height: 12px; border-radius: 50%;
37471
+ background: #fff; cursor: pointer; border: none;
37472
+ }
37473
+
37474
+ /* ── Time display ──────────────────────────────────────────── */
37475
+ .gvp-time {
37476
+ font-size: 12px; color: rgba(255,255,255,.85);
37477
+ font-variant-numeric: tabular-nums; white-space: nowrap; letter-spacing: .02em;
37478
+ }
37479
+ .gvp-spacer { flex: 1; }
37480
+
37481
+ /* ── Popup menus ───────────────────────────────────────────── */
37482
+ .gvp-menu-wrap { position: relative; }
37483
+ .gvp-menu {
37484
+ position: absolute; bottom: calc(100% + 8px); right: 0;
37485
+ background: rgba(18,18,22,.95);
37486
+ backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
37487
+ border: 1px solid rgba(255,255,255,.08); border-radius: 8px;
37488
+ min-width: 140px; overflow: hidden;
37489
+ box-shadow: 0 8px 24px rgba(0,0,0,.5);
37490
+ animation: gvp-menu-in .1s ease-out; z-index: 20;
37491
+ }
37492
+ @keyframes gvp-menu-in {
37493
+ from { opacity: 0; transform: scale(.95) translateY(4px); }
37494
+ to { opacity: 1; transform: none; }
37495
+ }
37496
+ .gvp-menu-title {
37497
+ font-size: 10px; font-weight: 600; text-transform: uppercase;
37498
+ letter-spacing: .08em; color: rgba(255,255,255,.4); padding: 8px 12px 4px;
37499
+ }
37500
+ .gvp-menu-item {
37501
+ display: flex; align-items: center; justify-content: space-between;
37502
+ padding: 7px 12px; font-size: 13px; color: rgba(255,255,255,.85);
37503
+ cursor: pointer; transition: background .12s; gap: 10px;
37504
+ }
37505
+ .gvp-menu-item:hover { background: rgba(255,255,255,.07); }
37506
+ .gvp-menu-item-active { color: var(--gvp-accent); font-weight: 600; }
37507
+ .gvp-menu-check { font-size: 14px; color: var(--gvp-accent); }
37508
+ .gvp-menu-sep { height: 1px; background: rgba(255,255,255,.07); margin: 3px 0; }
37509
+
37510
+ /* ── Error overlay ─────────────────────────────────────────── */
37511
+ .gvp-error {
37512
+ position: absolute; inset: 0; z-index: 15;
37513
+ display: flex; flex-direction: column; align-items: center; justify-content: center;
37514
+ background: rgba(0,0,0,.8); color: #fff; gap: 10px; padding: 24px;
37515
+ border-radius: 10px; text-align: center;
37516
+ }
37517
+ .gvp-error-icon { font-size: 36px; }
37518
+ .gvp-error-code { font-size: 11px; font-weight: 700; letter-spacing: .1em; color: #f87171; text-transform: uppercase; }
37519
+ .gvp-error-msg { font-size: 14px; color: rgba(255,255,255,.7); max-width: 320px; line-height: 1.5; }
37520
+
37521
+ /* ── Secure badge ──────────────────────────────────────────── */
37522
+ .gvp-badge {
37523
+ position: absolute; top: 10px; right: 12px; z-index: 5;
37524
+ display: flex; align-items: center; gap: 5px;
37525
+ background: rgba(0,0,0,.55); backdrop-filter: blur(6px);
37526
+ border-radius: 20px; padding: 3px 9px 3px 7px;
37527
+ font-size: 10px; font-weight: 600; color: var(--gvp-accent);
37528
+ pointer-events: none; letter-spacing: .04em; transition: opacity .3s;
37529
+ }
37530
+ .gvp-badge-hidden { opacity: 0; }
37531
+
37532
+ /* ── Forensic watermark ────────────────────────────────────── */
37533
+ .gvp-watermark { position: absolute; inset: 0; pointer-events: none; overflow: hidden; z-index: 6; }
37534
+ .gvp-watermark-text {
37535
+ position: absolute; white-space: nowrap; font-size: 13px; font-family: monospace;
37536
+ color: rgba(255,255,255,.065); transform: rotate(-28deg);
37537
+ user-select: none; pointer-events: none; letter-spacing: .05em;
37538
+ }
37539
+
37540
+ /* ── Speed label button ────────────────────────────────────── */
37541
+ .gvp-rate-label {
37542
+ font-size: 11px; font-weight: 700; color: rgba(255,255,255,.7);
37543
+ min-width: 28px; text-align: center; cursor: pointer;
37544
+ padding: 5px 4px; border-radius: 4px; transition: background .12s; flex-shrink: 0;
37545
+ }
37546
+ .gvp-rate-label:hover { background: rgba(255,255,255,.1); }
37547
+ `;
37548
+ const tag = document.createElement('style');
37549
+ tag.setAttribute('data-guardvideo', 'player-ui-styles');
37550
+ tag.textContent = css;
37551
+ document.head.appendChild(tag);
37552
+ }
37553
+ class PlayerUI {
37554
+ constructor(container, videoId, config) {
37555
+ this.playerState = exports.PlayerState.IDLE;
37556
+ this.duration = 0;
37557
+ this.bufferedEnd = 0;
37558
+ this.qualityLevels = [];
37559
+ this.currentQualityIdx = -1;
37560
+ this.playbackRate = 1;
37561
+ this.openMenu = null;
37562
+ this.hideTimer = null;
37563
+ this.seekDragging = false;
37564
+ const accent = config.branding?.accentColor ?? '#44c09b';
37565
+ const brandName = config.branding?.name ?? 'GuardVideo';
37566
+ injectStyles();
37296
37567
  container.innerHTML = '';
37297
- container.style.position = 'relative';
37298
- container.style.backgroundColor = '#000';
37299
- container.appendChild(videoElement);
37300
- return new GuardVideoPlayer(videoElement, videoId, config);
37568
+ this.root = el('div', 'gvp-root');
37569
+ this.root.style.width = config.width ?? '100%';
37570
+ this.root.style.height = config.height ?? 'auto';
37571
+ this.root.style.setProperty('--gvp-accent', accent);
37572
+ this.root.setAttribute('tabindex', '0');
37573
+ this.root.setAttribute('role', 'region');
37574
+ this.root.setAttribute('aria-label', `${brandName} video player`);
37575
+ this.videoEl = el('video', 'gvp-video', { playsinline: '', preload: 'metadata' });
37576
+ this.badge = el('div', 'gvp-badge');
37577
+ this.badge.setAttribute('aria-hidden', 'true');
37578
+ this.badge.appendChild(svgEl(ICON.shield, 14, 14));
37579
+ this.badge.appendChild(document.createTextNode(brandName));
37580
+ this.watermarkDiv = el('div', 'gvp-watermark');
37581
+ this.watermarkDiv.setAttribute('aria-hidden', 'true');
37582
+ this.spinner = el('div', 'gvp-spinner gvp-hidden');
37583
+ this.spinner.setAttribute('aria-label', 'Loading');
37584
+ this.spinner.setAttribute('role', 'status');
37585
+ this.spinner.appendChild(el('div', 'gvp-spinner-ring'));
37586
+ this.errorOverlay = el('div', 'gvp-error gvp-hidden');
37587
+ this.errorOverlay.setAttribute('role', 'alert');
37588
+ const errIcon = el('div', 'gvp-error-icon');
37589
+ errIcon.textContent = '⚠️';
37590
+ errIcon.setAttribute('aria-hidden', 'true');
37591
+ const errCode = el('div', 'gvp-error-code');
37592
+ const errMsg = el('div', 'gvp-error-msg');
37593
+ this.errorOverlay.append(errIcon, errCode, errMsg);
37594
+ this.centerPlay = el('div', 'gvp-center-play');
37595
+ this.centerPlay.setAttribute('role', 'button');
37596
+ this.centerPlay.setAttribute('aria-label', 'Play');
37597
+ this.centerPlayBtn = el('div', 'gvp-center-play-btn');
37598
+ this.centerPlayBtn.appendChild(svgEl(ICON.play, 32, 32));
37599
+ this.centerPlay.appendChild(this.centerPlayBtn);
37600
+ this.clickArea = el('div', 'gvp-click-area gvp-hidden');
37601
+ this.clickArea.setAttribute('aria-hidden', 'true');
37602
+ this.controls = el('div', 'gvp-controls');
37603
+ const seekRow = el('div', 'gvp-seek-row');
37604
+ this.seekWrap = el('div', 'gvp-seek-wrap');
37605
+ this.seekWrap.setAttribute('role', 'slider');
37606
+ this.seekWrap.setAttribute('aria-label', 'Seek');
37607
+ this.seekWrap.setAttribute('aria-valuemin', '0');
37608
+ this.seekWrap.setAttribute('aria-valuemax', '100');
37609
+ this.seekWrap.setAttribute('aria-valuenow', '0');
37610
+ this.seekWrap.setAttribute('aria-valuetext', '0:00 of 0:00');
37611
+ this.seekWrap.setAttribute('tabindex', '0');
37612
+ const seekTrack = el('div', 'gvp-seek-track');
37613
+ this.seekBuffered = el('div', 'gvp-seek-buffered');
37614
+ this.seekProgress = el('div', 'gvp-seek-progress');
37615
+ this.seekThumb = el('div', 'gvp-seek-thumb');
37616
+ this.seekTooltip = el('div', 'gvp-seek-tooltip');
37617
+ this.seekTooltip.setAttribute('aria-hidden', 'true');
37618
+ seekTrack.append(this.seekBuffered, this.seekProgress);
37619
+ this.seekWrap.append(seekTrack, this.seekThumb, this.seekTooltip);
37620
+ seekRow.appendChild(this.seekWrap);
37621
+ this.controls.appendChild(seekRow);
37622
+ const btnRow = el('div', 'gvp-btn-row');
37623
+ this.playBtn = el('button', 'gvp-btn');
37624
+ this.playBtn.type = 'button';
37625
+ this.playBtn.setAttribute('aria-label', 'Play');
37626
+ const volWrap = el('div', 'gvp-volume-wrap');
37627
+ this.volBtn = el('button', 'gvp-btn');
37628
+ this.volBtn.type = 'button';
37629
+ this.volBtn.setAttribute('aria-label', 'Mute');
37630
+ this.volBtn.appendChild(svgEl(ICON.volHigh, 18, 18));
37631
+ this.volSlider = el('input', 'gvp-volume-slider');
37632
+ Object.assign(this.volSlider, { type: 'range', min: '0', max: '1', step: '0.02', value: '1' });
37633
+ this.volSlider.setAttribute('aria-label', 'Volume');
37634
+ volWrap.append(this.volBtn, this.volSlider);
37635
+ this.timeEl = el('span', 'gvp-time');
37636
+ this.timeEl.setAttribute('aria-live', 'off');
37637
+ this.timeEl.textContent = '0:00 / 0:00';
37638
+ const spacer = el('div', 'gvp-spacer');
37639
+ const speedWrap = el('div', 'gvp-menu-wrap');
37640
+ this.speedLabel = el('span', 'gvp-rate-label');
37641
+ this.speedLabel.textContent = '1×';
37642
+ this.speedLabel.setAttribute('role', 'button');
37643
+ this.speedLabel.setAttribute('aria-label', 'Playback speed');
37644
+ this.speedLabel.setAttribute('aria-haspopup', 'menu');
37645
+ this.speedMenu = el('div', 'gvp-menu gvp-hidden');
37646
+ this.speedMenu.setAttribute('role', 'menu');
37647
+ this.speedMenu.setAttribute('aria-label', 'Playback speed');
37648
+ const speedTitle = el('div', 'gvp-menu-title');
37649
+ speedTitle.setAttribute('aria-hidden', 'true');
37650
+ speedTitle.textContent = 'Speed';
37651
+ this.speedMenu.appendChild(speedTitle);
37652
+ SPEEDS.forEach(r => {
37653
+ const item = el('div', r === 1 ? 'gvp-menu-item gvp-menu-item-active' : 'gvp-menu-item');
37654
+ item.setAttribute('role', 'menuitemradio');
37655
+ item.setAttribute('aria-checked', r === 1 ? 'true' : 'false');
37656
+ item.dataset['speed'] = String(r);
37657
+ item.textContent = r === 1 ? 'Normal' : `${r}×`;
37658
+ if (r === 1) {
37659
+ const check = el('span', 'gvp-menu-check');
37660
+ check.setAttribute('aria-hidden', 'true');
37661
+ check.textContent = '✓';
37662
+ item.appendChild(check);
37663
+ }
37664
+ this.speedMenu.appendChild(item);
37665
+ });
37666
+ speedWrap.append(this.speedLabel, this.speedMenu);
37667
+ const qualWrap = el('div', 'gvp-menu-wrap');
37668
+ this.settingsBtn = el('button', 'gvp-btn');
37669
+ this.settingsBtn.type = 'button';
37670
+ this.settingsBtn.setAttribute('aria-label', 'Quality settings');
37671
+ this.settingsBtn.setAttribute('aria-haspopup', 'menu');
37672
+ this.settingsBtn.appendChild(svgEl(ICON.settings, 18, 18));
37673
+ this.settingsBtn.classList.add('gvp-hidden');
37674
+ this.qualityMenu = el('div', 'gvp-menu gvp-hidden');
37675
+ this.qualityMenu.setAttribute('role', 'menu');
37676
+ this.qualityMenu.setAttribute('aria-label', 'Video quality');
37677
+ qualWrap.append(this.settingsBtn, this.qualityMenu);
37678
+ this.fsBtn = el('button', 'gvp-btn');
37679
+ this.fsBtn.type = 'button';
37680
+ this.fsBtn.setAttribute('aria-label', 'Enter fullscreen');
37681
+ this.fsBtn.appendChild(svgEl(ICON.fsEnter, 18, 18));
37682
+ this.playBtn.appendChild(svgEl(ICON.play));
37683
+ btnRow.append(this.playBtn, volWrap, this.timeEl, spacer, speedWrap, qualWrap, this.fsBtn);
37684
+ this.controls.appendChild(btnRow);
37685
+ this.root.append(this.videoEl, this.badge, this.watermarkDiv, this.spinner, this.errorOverlay, this.centerPlay, this.clickArea, this.controls);
37686
+ container.appendChild(this.root);
37687
+ this._onFsChangeBound = () => this._onFsChange();
37688
+ this._seekMouseMoveBound = (e) => { if (this.seekDragging)
37689
+ this._seekTo(e.clientX); };
37690
+ this._seekMouseUpBound = () => this._endSeekDrag();
37691
+ this._seekTouchMoveBound = (e) => {
37692
+ if (!this.seekDragging)
37693
+ return;
37694
+ e.preventDefault();
37695
+ this._seekTo(e.touches[0].clientX);
37696
+ };
37697
+ this._seekTouchEndBound = () => this._endSeekDrag();
37698
+ this._wireEvents(videoId, config);
37699
+ if (config.forensicWatermark !== false) {
37700
+ const wmText = config.viewerEmail || config.viewerName || '';
37701
+ if (wmText)
37702
+ this._renderWatermark(wmText);
37703
+ }
37704
+ }
37705
+ _wireEvents(videoId, config) {
37706
+ const video = this.videoEl;
37707
+ this.corePlayer = new GuardVideoPlayer$1(video, videoId, {
37708
+ ...config,
37709
+ controls: false,
37710
+ forensicWatermark: config.forensicWatermark !== false,
37711
+ onReady: () => {
37712
+ config.onReady?.();
37713
+ const levels = this.corePlayer.getQualityLevels();
37714
+ if (levels.length) {
37715
+ this.qualityLevels = levels;
37716
+ this._buildQualityMenu();
37717
+ }
37718
+ else {
37719
+ setTimeout(() => {
37720
+ this.qualityLevels = this.corePlayer.getQualityLevels();
37721
+ if (this.qualityLevels.length)
37722
+ this._buildQualityMenu();
37723
+ }, 100);
37724
+ }
37725
+ },
37726
+ onError: (err) => {
37727
+ this._showError(err);
37728
+ config.onError?.(err);
37729
+ },
37730
+ onQualityChange: (quality) => {
37731
+ const idx = this.qualityLevels.findIndex(l => l.name === quality);
37732
+ this.currentQualityIdx = idx;
37733
+ this._refreshQualityMenu();
37734
+ config.onQualityChange?.(quality);
37735
+ },
37736
+ onStateChange: (state) => {
37737
+ this._onStateChange(state);
37738
+ config.onStateChange?.(state);
37739
+ },
37740
+ });
37741
+ video.addEventListener('timeupdate', () => {
37742
+ config.onTimeUpdate?.(video.currentTime);
37743
+ this._onTimeUpdate();
37744
+ });
37745
+ video.addEventListener('loadedmetadata', () => { this.duration = video.duration || 0; this._onTimeUpdate(); });
37746
+ video.addEventListener('durationchange', () => { this.duration = video.duration || 0; });
37747
+ video.addEventListener('progress', () => {
37748
+ if (video.buffered.length > 0) {
37749
+ this.bufferedEnd = video.buffered.end(video.buffered.length - 1);
37750
+ this._updateSeekBar();
37751
+ }
37752
+ });
37753
+ video.addEventListener('volumechange', () => this._onVolumeChange());
37754
+ video.addEventListener('ended', () => {
37755
+ config.onEnded?.();
37756
+ this._showControls(true);
37757
+ this._renderPlayBtn();
37758
+ });
37759
+ video.addEventListener('ratechange', () => {
37760
+ this.playbackRate = video.playbackRate;
37761
+ this.speedLabel.textContent = this.playbackRate === 1 ? '1×' : `${this.playbackRate}×`;
37762
+ });
37763
+ this.playBtn.addEventListener('click', (e) => { e.stopPropagation(); this._togglePlay(); });
37764
+ this.volBtn.addEventListener('click', (e) => { e.stopPropagation(); this._toggleMute(); });
37765
+ this.volSlider.addEventListener('input', () => {
37766
+ video.volume = parseFloat(this.volSlider.value);
37767
+ video.muted = video.volume === 0;
37768
+ });
37769
+ this.seekWrap.addEventListener('mousedown', (e) => {
37770
+ e.preventDefault();
37771
+ this._startSeekDrag();
37772
+ this._seekTo(e.clientX);
37773
+ window.addEventListener('mousemove', this._seekMouseMoveBound);
37774
+ window.addEventListener('mouseup', this._seekMouseUpBound);
37775
+ });
37776
+ this.seekWrap.addEventListener('mousemove', (e) => this._onSeekHover(e.clientX));
37777
+ this.seekWrap.addEventListener('mouseleave', () => { this.seekTooltip.style.opacity = '0'; });
37778
+ this.seekWrap.addEventListener('touchstart', (e) => {
37779
+ e.preventDefault();
37780
+ this._startSeekDrag();
37781
+ this._seekTo(e.touches[0].clientX);
37782
+ window.addEventListener('touchmove', this._seekTouchMoveBound, { passive: false });
37783
+ window.addEventListener('touchend', this._seekTouchEndBound);
37784
+ }, { passive: false });
37785
+ this.seekWrap.addEventListener('keydown', (e) => {
37786
+ if (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight')
37787
+ return;
37788
+ e.preventDefault();
37789
+ const delta = e.key === 'ArrowLeft' ? -5 : 5;
37790
+ this.corePlayer.seek(Math.max(0, Math.min(this.duration, video.currentTime + delta)));
37791
+ this._resetHideTimer();
37792
+ });
37793
+ this.speedLabel.addEventListener('click', (e) => { e.stopPropagation(); this._toggleMenu('speed'); });
37794
+ this.speedMenu.addEventListener('click', (e) => {
37795
+ e.stopPropagation();
37796
+ const item = e.target.closest('[data-speed]');
37797
+ if (!item)
37798
+ return;
37799
+ video.playbackRate = parseFloat(item.dataset['speed']);
37800
+ this._closeMenus();
37801
+ });
37802
+ this.settingsBtn.addEventListener('click', (e) => { e.stopPropagation(); this._toggleMenu('quality'); });
37803
+ this.qualityMenu.addEventListener('click', (e) => {
37804
+ e.stopPropagation();
37805
+ const item = e.target.closest('[data-quality]');
37806
+ if (!item)
37807
+ return;
37808
+ const idx = parseInt(item.dataset['quality']);
37809
+ this.corePlayer.setQuality(idx);
37810
+ this.currentQualityIdx = idx;
37811
+ this._refreshQualityMenu();
37812
+ this._closeMenus();
37813
+ });
37814
+ this.clickArea.addEventListener('click', (e) => { this._addRipple(e); this._togglePlay(); });
37815
+ this.centerPlay.addEventListener('click', (e) => { e.stopPropagation(); this._togglePlay(); });
37816
+ this.fsBtn.addEventListener('click', (e) => { e.stopPropagation(); this._toggleFullscreen(); });
37817
+ document.addEventListener('fullscreenchange', this._onFsChangeBound);
37818
+ this.root.addEventListener('mousemove', () => this._resetHideTimer());
37819
+ this.root.addEventListener('touchstart', () => this._resetHideTimer(), { passive: true });
37820
+ this.root.addEventListener('mouseleave', () => {
37821
+ if (this.playerState === exports.PlayerState.PLAYING)
37822
+ this._showControls(false);
37823
+ });
37824
+ this.root.addEventListener('click', () => this._closeMenus());
37825
+ this.root.addEventListener('keydown', (e) => this._onKeyDown(e));
37826
+ }
37827
+ _onStateChange(state) {
37828
+ this.playerState = state;
37829
+ const loading = state === exports.PlayerState.LOADING || state === exports.PlayerState.BUFFERING;
37830
+ const idle = state === exports.PlayerState.IDLE;
37831
+ const ended = this.videoEl.ended;
37832
+ this.spinner.classList.toggle('gvp-hidden', !loading);
37833
+ const showCenter = (idle || ended) && !loading;
37834
+ this.centerPlay.classList.toggle('gvp-hidden', !showCenter);
37835
+ if (showCenter) {
37836
+ this.centerPlayBtn.innerHTML = '';
37837
+ this.centerPlayBtn.appendChild(svgEl(ended ? ICON.replay : ICON.play, 32, 32));
37838
+ this.centerPlay.setAttribute('aria-label', ended ? 'Replay' : 'Play');
37839
+ }
37840
+ this.clickArea.classList.toggle('gvp-hidden', idle);
37841
+ if (state !== exports.PlayerState.PLAYING) {
37842
+ this._showControls(true);
37843
+ if (this.hideTimer) {
37844
+ clearTimeout(this.hideTimer);
37845
+ this.hideTimer = null;
37846
+ }
37847
+ }
37848
+ else {
37849
+ this._resetHideTimer();
37850
+ }
37851
+ this._renderPlayBtn();
37301
37852
  }
37302
- static createFromElement(videoElement, videoId, config) {
37303
- const element = typeof videoElement === 'string'
37304
- ? document.getElementById(videoElement)
37305
- : videoElement;
37306
- if (!element || element.tagName !== 'VIDEO') {
37307
- throw new Error('Invalid video element provided');
37853
+ _togglePlay() {
37854
+ const v = this.videoEl;
37855
+ if (v.paused || v.ended) {
37856
+ this.corePlayer.play().catch(() => { });
37308
37857
  }
37309
- return new GuardVideoPlayer(element, videoId, config);
37858
+ else {
37859
+ this.corePlayer.pause();
37860
+ }
37861
+ this._resetHideTimer();
37862
+ }
37863
+ _renderPlayBtn() {
37864
+ const ended = this.videoEl.ended;
37865
+ const playing = this.playerState === exports.PlayerState.PLAYING;
37866
+ const icon = ended ? ICON.replay : playing ? ICON.pause : ICON.play;
37867
+ const label = playing ? 'Pause (k)' : 'Play (k)';
37868
+ this.playBtn.innerHTML = '';
37869
+ this.playBtn.appendChild(svgEl(icon));
37870
+ this.playBtn.setAttribute('aria-label', label);
37871
+ this.playBtn.title = label;
37872
+ }
37873
+ _toggleMute() { this.videoEl.muted = !this.videoEl.muted; }
37874
+ _onVolumeChange() {
37875
+ const v = this.videoEl;
37876
+ const muted = v.muted || v.volume === 0;
37877
+ this.volBtn.innerHTML = '';
37878
+ this.volBtn.appendChild(svgEl(muted ? ICON.volMute : ICON.volHigh, 18, 18));
37879
+ this.volBtn.setAttribute('aria-label', muted ? 'Unmute' : 'Mute');
37880
+ this.volSlider.value = String(muted ? 0 : v.volume);
37881
+ }
37882
+ _startSeekDrag() {
37883
+ this.seekDragging = true;
37884
+ this.seekWrap.classList.add('gvp-dragging');
37885
+ }
37886
+ _endSeekDrag() {
37887
+ this.seekDragging = false;
37888
+ this.seekWrap.classList.remove('gvp-dragging');
37889
+ window.removeEventListener('mousemove', this._seekMouseMoveBound);
37890
+ window.removeEventListener('mouseup', this._seekMouseUpBound);
37891
+ window.removeEventListener('touchmove', this._seekTouchMoveBound);
37892
+ window.removeEventListener('touchend', this._seekTouchEndBound);
37893
+ this._resetHideTimer();
37894
+ }
37895
+ _seekTo(clientX) {
37896
+ if (!this.duration)
37897
+ return;
37898
+ const rect = this.seekWrap.getBoundingClientRect();
37899
+ const pct = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));
37900
+ this.corePlayer.seek(pct * this.duration);
37901
+ }
37902
+ _onSeekHover(clientX) {
37903
+ if (!this.duration)
37904
+ return;
37905
+ const rect = this.seekWrap.getBoundingClientRect();
37906
+ const pct = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));
37907
+ this.seekTooltip.textContent = formatTime(pct * this.duration);
37908
+ this.seekTooltip.style.left = `${pct * 100}%`;
37909
+ this.seekTooltip.style.opacity = '1';
37910
+ }
37911
+ _onTimeUpdate() {
37912
+ const ct = this.videoEl.currentTime;
37913
+ const dur = this.duration;
37914
+ this.timeEl.textContent = `${formatTime(ct)} / ${formatTime(dur)}`;
37915
+ this._updateSeekBar();
37916
+ const pct = dur > 0 ? Math.round((ct / dur) * 100) : 0;
37917
+ this.seekWrap.setAttribute('aria-valuenow', String(pct));
37918
+ this.seekWrap.setAttribute('aria-valuetext', `${formatTime(ct)} of ${formatTime(dur)}`);
37919
+ }
37920
+ _updateSeekBar() {
37921
+ const ct = this.videoEl.currentTime;
37922
+ const dur = this.duration;
37923
+ if (!dur)
37924
+ return;
37925
+ const pPct = (ct / dur) * 100;
37926
+ const bPct = (this.bufferedEnd / dur) * 100;
37927
+ this.seekProgress.style.width = `${pPct}%`;
37928
+ this.seekBuffered.style.width = `${bPct}%`;
37929
+ this.seekThumb.style.left = `${pPct}%`;
37930
+ }
37931
+ _buildQualityMenu() {
37932
+ if (!this.qualityLevels.length)
37933
+ return;
37934
+ this.settingsBtn.classList.remove('gvp-hidden');
37935
+ this.qualityMenu.innerHTML = '';
37936
+ const title = el('div', 'gvp-menu-title');
37937
+ title.setAttribute('aria-hidden', 'true');
37938
+ title.textContent = 'Quality';
37939
+ const autoItem = el('div', 'gvp-menu-item gvp-menu-item-active');
37940
+ autoItem.setAttribute('role', 'menuitemradio');
37941
+ autoItem.setAttribute('aria-checked', 'true');
37942
+ autoItem.dataset['quality'] = '-1';
37943
+ autoItem.textContent = 'Auto';
37944
+ const checkEl = el('span', 'gvp-menu-check');
37945
+ checkEl.setAttribute('aria-hidden', 'true');
37946
+ checkEl.textContent = '✓';
37947
+ autoItem.appendChild(checkEl);
37948
+ this.qualityMenu.append(title, autoItem, el('div', 'gvp-menu-sep'));
37949
+ [...this.qualityLevels].reverse().forEach(q => {
37950
+ const item = el('div', 'gvp-menu-item');
37951
+ item.setAttribute('role', 'menuitemradio');
37952
+ item.setAttribute('aria-checked', 'false');
37953
+ item.dataset['quality'] = String(q.index);
37954
+ item.textContent = q.name;
37955
+ this.qualityMenu.appendChild(item);
37956
+ });
37957
+ this.currentQualityIdx = -1;
37958
+ }
37959
+ _refreshQualityMenu() {
37960
+ this.qualityMenu.querySelectorAll('[data-quality]').forEach(item => {
37961
+ const active = parseInt(item.dataset['quality']) === this.currentQualityIdx;
37962
+ item.className = active ? 'gvp-menu-item gvp-menu-item-active' : 'gvp-menu-item';
37963
+ item.setAttribute('aria-checked', String(active));
37964
+ const existing = item.querySelector('.gvp-menu-check');
37965
+ if (active && !existing) {
37966
+ const c = el('span', 'gvp-menu-check');
37967
+ c.setAttribute('aria-hidden', 'true');
37968
+ c.textContent = '✓';
37969
+ item.appendChild(c);
37970
+ }
37971
+ else if (!active && existing) {
37972
+ existing.remove();
37973
+ }
37974
+ });
37310
37975
  }
37976
+ _refreshSpeedMenu() {
37977
+ this.speedMenu.querySelectorAll('[data-speed]').forEach(item => {
37978
+ const active = parseFloat(item.dataset['speed']) === this.playbackRate;
37979
+ item.className = active ? 'gvp-menu-item gvp-menu-item-active' : 'gvp-menu-item';
37980
+ item.setAttribute('aria-checked', String(active));
37981
+ const existing = item.querySelector('.gvp-menu-check');
37982
+ if (active && !existing) {
37983
+ const c = el('span', 'gvp-menu-check');
37984
+ c.setAttribute('aria-hidden', 'true');
37985
+ c.textContent = '✓';
37986
+ item.appendChild(c);
37987
+ }
37988
+ else if (!active && existing) {
37989
+ existing.remove();
37990
+ }
37991
+ });
37992
+ }
37993
+ _toggleMenu(which) {
37994
+ const same = this.openMenu === which;
37995
+ this._closeMenus();
37996
+ if (!same) {
37997
+ this.openMenu = which;
37998
+ if (which === 'speed') {
37999
+ this._refreshSpeedMenu();
38000
+ this.speedMenu.classList.remove('gvp-hidden');
38001
+ }
38002
+ else {
38003
+ this._refreshQualityMenu();
38004
+ this.qualityMenu.classList.remove('gvp-hidden');
38005
+ }
38006
+ }
38007
+ }
38008
+ _closeMenus() {
38009
+ this.openMenu = null;
38010
+ this.speedMenu.classList.add('gvp-hidden');
38011
+ this.qualityMenu.classList.add('gvp-hidden');
38012
+ }
38013
+ _showControls(visible) {
38014
+ this.controls.classList.toggle('gvp-controls-hidden', !visible);
38015
+ this.badge.classList.toggle('gvp-badge-hidden', !visible);
38016
+ }
38017
+ _resetHideTimer() {
38018
+ this._showControls(true);
38019
+ if (this.hideTimer)
38020
+ clearTimeout(this.hideTimer);
38021
+ this.hideTimer = setTimeout(() => {
38022
+ if (this.playerState === exports.PlayerState.PLAYING && !this.openMenu) {
38023
+ this._showControls(false);
38024
+ }
38025
+ }, 2800);
38026
+ }
38027
+ _toggleFullscreen() {
38028
+ if (document.fullscreenElement) {
38029
+ document.exitFullscreen().catch(() => { });
38030
+ }
38031
+ else {
38032
+ this.root.requestFullscreen().catch(() => { });
38033
+ }
38034
+ }
38035
+ _onFsChange() {
38036
+ const fs = !!document.fullscreenElement;
38037
+ this.fsBtn.innerHTML = '';
38038
+ this.fsBtn.appendChild(svgEl(fs ? ICON.fsExit : ICON.fsEnter, 18, 18));
38039
+ this.fsBtn.setAttribute('aria-label', fs ? 'Exit fullscreen' : 'Enter fullscreen');
38040
+ this.fsBtn.title = fs ? 'Exit fullscreen (f)' : 'Enter fullscreen (f)';
38041
+ }
38042
+ _showError(err) {
38043
+ this.errorOverlay.children[1].textContent = err.code;
38044
+ this.errorOverlay.children[2].textContent = err.message;
38045
+ this.errorOverlay.classList.remove('gvp-hidden');
38046
+ this.controls.classList.add('gvp-hidden');
38047
+ }
38048
+ _renderWatermark(text) {
38049
+ this.watermarkDiv.innerHTML = '';
38050
+ for (let i = 0; i < 20; i++) {
38051
+ const span = el('span', 'gvp-watermark-text');
38052
+ span.textContent = text;
38053
+ span.style.left = `${(i % 4) * 26 + (Math.floor(i / 4) % 2) * 13}%`;
38054
+ span.style.top = `${Math.floor(i / 4) * 22}%`;
38055
+ this.watermarkDiv.appendChild(span);
38056
+ }
38057
+ }
38058
+ _addRipple(e) {
38059
+ const rect = this.root.getBoundingClientRect();
38060
+ const d = document.createElement('div');
38061
+ d.className = 'gvp-ripple';
38062
+ const sz = 60;
38063
+ d.style.cssText = `width:${sz}px;height:${sz}px;left:${e.clientX - rect.left - sz / 2}px;top:${e.clientY - rect.top - sz / 2}px`;
38064
+ this.root.appendChild(d);
38065
+ setTimeout(() => d.remove(), 600);
38066
+ }
38067
+ _onKeyDown(e) {
38068
+ switch (e.code) {
38069
+ case 'Space':
38070
+ case 'KeyK':
38071
+ e.preventDefault();
38072
+ this._togglePlay();
38073
+ break;
38074
+ case 'ArrowLeft':
38075
+ if (document.activeElement !== this.seekWrap) {
38076
+ e.preventDefault();
38077
+ this.corePlayer.seek(Math.max(0, this.videoEl.currentTime - 5));
38078
+ }
38079
+ break;
38080
+ case 'ArrowRight':
38081
+ if (document.activeElement !== this.seekWrap) {
38082
+ e.preventDefault();
38083
+ this.corePlayer.seek(Math.min(this.duration, this.videoEl.currentTime + 5));
38084
+ }
38085
+ break;
38086
+ case 'ArrowUp':
38087
+ e.preventDefault();
38088
+ this.videoEl.volume = Math.min(1, this.videoEl.volume + 0.1);
38089
+ break;
38090
+ case 'ArrowDown':
38091
+ e.preventDefault();
38092
+ this.videoEl.volume = Math.max(0, this.videoEl.volume - 0.1);
38093
+ break;
38094
+ case 'KeyM':
38095
+ e.preventDefault();
38096
+ this._toggleMute();
38097
+ break;
38098
+ case 'KeyF':
38099
+ e.preventDefault();
38100
+ this._toggleFullscreen();
38101
+ break;
38102
+ }
38103
+ }
38104
+ play() { return this.corePlayer.play(); }
38105
+ pause() { return this.corePlayer.pause(); }
38106
+ seek(t) { return this.corePlayer.seek(t); }
38107
+ getCurrentTime() { return this.corePlayer.getCurrentTime(); }
38108
+ getDuration() { return this.corePlayer.getDuration(); }
38109
+ getVolume() { return this.corePlayer.getVolume(); }
38110
+ setVolume(v) { return this.corePlayer.setVolume(v); }
38111
+ getQualityLevels() { return this.corePlayer.getQualityLevels(); }
38112
+ getCurrentQuality() { return this.corePlayer.getCurrentQuality(); }
38113
+ setQuality(i) { return this.corePlayer.setQuality(i); }
38114
+ getState() { return this.corePlayer.getState(); }
38115
+ getVideoElement() { return this.videoEl; }
38116
+ destroy() {
38117
+ if (this.hideTimer)
38118
+ clearTimeout(this.hideTimer);
38119
+ document.removeEventListener('fullscreenchange', this._onFsChangeBound);
38120
+ window.removeEventListener('mousemove', this._seekMouseMoveBound);
38121
+ window.removeEventListener('mouseup', this._seekMouseUpBound);
38122
+ window.removeEventListener('touchmove', this._seekTouchMoveBound);
38123
+ window.removeEventListener('touchend', this._seekTouchEndBound);
38124
+ this.corePlayer.destroy();
38125
+ this.root.remove();
38126
+ }
38127
+ }
38128
+
38129
+ class GuardVideoPlayer {
38130
+ constructor(ui) {
38131
+ this.ui = ui;
38132
+ }
38133
+ static create(containerId, videoId, config) {
38134
+ const container = document.getElementById(containerId);
38135
+ if (!container) {
38136
+ throw new Error(`GuardVideoPlayer: element '#${containerId}' not found`);
38137
+ }
38138
+ return new GuardVideoPlayer(new PlayerUI(container, videoId, config));
38139
+ }
38140
+ static createInElement(container, videoId, config) {
38141
+ return new GuardVideoPlayer(new PlayerUI(container, videoId, config));
38142
+ }
38143
+ play() { return this.ui.play(); }
38144
+ pause() { return this.ui.pause(); }
38145
+ seek(time) { return this.ui.seek(time); }
38146
+ getCurrentTime() { return this.ui.getCurrentTime(); }
38147
+ getDuration() { return this.ui.getDuration(); }
38148
+ getVolume() { return this.ui.getVolume(); }
38149
+ setVolume(volume) { return this.ui.setVolume(volume); }
38150
+ getQualityLevels() { return this.ui.getQualityLevels(); }
38151
+ getCurrentQuality() { return this.ui.getCurrentQuality(); }
38152
+ setQuality(idx) { return this.ui.setQuality(idx); }
38153
+ getState() { return this.ui.getState(); }
38154
+ getVideoElement() { return this.ui.getVideoElement(); }
38155
+ destroy() { return this.ui.destroy(); }
37311
38156
  }
37312
38157
  if (typeof window !== 'undefined') {
37313
38158
  window.GuardVideoPlayer = GuardVideoPlayer;
@@ -37315,6 +38160,7 @@ Schedule: ${scheduleItems.map(seg => segmentToString(seg))} pos: ${this.timeline
37315
38160
  }
37316
38161
 
37317
38162
  exports.GuardVideoPlayer = GuardVideoPlayer;
38163
+ exports.PlayerUI = PlayerUI;
37318
38164
 
37319
38165
  return exports;
37320
38166