unified-video-framework 1.4.129 → 1.4.131

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.
@@ -40,6 +40,14 @@ export class WebPlayer extends BasePlayer {
40
40
  private currentSubtitle = 'off';
41
41
  private currentPlaybackRate = 1;
42
42
  private isDragging: boolean = false;
43
+
44
+ // Settings configuration
45
+ private settingsConfig = {
46
+ enabled: true, // Show settings button
47
+ speed: true, // Show playback speed options
48
+ quality: true, // Show quality options
49
+ subtitles: true // Show subtitle options
50
+ };
43
51
  private watermarkCanvas: HTMLCanvasElement | null = null;
44
52
  private playerWrapper: HTMLElement | null = null;
45
53
  // Free preview gate state
@@ -96,6 +104,26 @@ export class WebPlayer extends BasePlayer {
96
104
  }
97
105
 
98
106
 
107
+ async initialize(container: HTMLElement | string, config?: any): Promise<void> {
108
+ // Set useCustomControls based on config before calling parent initialize
109
+ if (config && config.customControls !== undefined) {
110
+ this.useCustomControls = config.customControls;
111
+ }
112
+
113
+ // Configure settings menu options
114
+ if (config && config.settings) {
115
+ this.settingsConfig = {
116
+ enabled: config.settings.enabled !== undefined ? config.settings.enabled : true,
117
+ speed: config.settings.speed !== undefined ? config.settings.speed : true,
118
+ quality: config.settings.quality !== undefined ? config.settings.quality : true,
119
+ subtitles: config.settings.subtitles !== undefined ? config.settings.subtitles : true
120
+ };
121
+ }
122
+
123
+ // Call parent initialize
124
+ await super.initialize(container, config);
125
+ }
126
+
99
127
  protected async setupPlayer(): Promise<void> {
100
128
  if (!this.container) {
101
129
  throw new Error('Container element is required');
@@ -2278,6 +2306,7 @@ export class WebPlayer extends BasePlayer {
2278
2306
  overflow-y: auto;
2279
2307
  -webkit-overflow-scrolling: touch;
2280
2308
  overscroll-behavior: contain;
2309
+ z-index: 1000; /* Ensure settings menu appears above other elements */
2281
2310
  /* Firefox */
2282
2311
  scrollbar-width: thin;
2283
2312
  scrollbar-color: var(--uvf-firefox-scrollbar-color) transparent;
@@ -4228,8 +4257,14 @@ export class WebPlayer extends BasePlayer {
4228
4257
 
4229
4258
  /**
4230
4259
  * Creates framework branding logo
4260
+ * Only creates branding if showFrameworkBranding is not explicitly set to false
4231
4261
  */
4232
4262
  private createFrameworkBranding(container: HTMLElement): void {
4263
+ // Double-check configuration (defensive programming)
4264
+ if ((this.config as any).showFrameworkBranding === false) {
4265
+ this.debugLog('Framework branding disabled by configuration');
4266
+ return;
4267
+ }
4233
4268
  const brandingContainer = document.createElement('div');
4234
4269
  brandingContainer.className = 'uvf-framework-branding';
4235
4270
  brandingContainer.setAttribute('title', 'Powered by flicknexs');
@@ -4372,8 +4407,10 @@ export class WebPlayer extends BasePlayer {
4372
4407
  timeDisplay.textContent = '00:00 / 00:00';
4373
4408
  aboveSeekbarSection.appendChild(timeDisplay);
4374
4409
 
4375
- // Add framework branding next to time display
4376
- this.createFrameworkBranding(aboveSeekbarSection);
4410
+ // Add framework branding next to time display if enabled
4411
+ if ((this.config as any).showFrameworkBranding !== false) {
4412
+ this.createFrameworkBranding(aboveSeekbarSection);
4413
+ }
4377
4414
 
4378
4415
  // Progress section
4379
4416
  const progressSection = document.createElement('div');
@@ -4457,25 +4494,29 @@ export class WebPlayer extends BasePlayer {
4457
4494
  qualityBadge.textContent = 'HD';
4458
4495
  rightControls.appendChild(qualityBadge);
4459
4496
 
4460
- // Settings button with menu
4461
- const settingsContainer = document.createElement('div');
4462
- settingsContainer.style.position = 'relative';
4463
-
4464
- const settingsBtn = document.createElement('button');
4465
- settingsBtn.className = 'uvf-control-btn';
4466
- settingsBtn.id = 'uvf-settings-btn';
4467
- settingsBtn.innerHTML = '<svg viewBox="0 0 24 24"><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.61 l-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.41 h-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.87 C2.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.58 c-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-0.96c0.5,0.38,1.03,0.7,1.62,0.94l0.36,2.54 c0.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.96 c0.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.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z"/></svg>';
4468
- settingsContainer.appendChild(settingsBtn);
4469
-
4470
- // Settings menu - will be populated dynamically based on video capabilities
4471
- const settingsMenu = document.createElement('div');
4472
- settingsMenu.className = 'uvf-settings-menu';
4473
- settingsMenu.id = 'uvf-settings-menu';
4474
- // Initially empty - will be populated by updateSettingsMenu method
4475
- settingsMenu.innerHTML = '';
4476
- settingsMenu.style.display = 'none';
4477
- settingsContainer.appendChild(settingsMenu);
4478
- rightControls.appendChild(settingsContainer);
4497
+ // Settings button with menu (show only if enabled)
4498
+ if (this.settingsConfig.enabled) {
4499
+ const settingsContainer = document.createElement('div');
4500
+ settingsContainer.style.position = 'relative';
4501
+
4502
+ const settingsBtn = document.createElement('button');
4503
+ settingsBtn.className = 'uvf-control-btn';
4504
+ settingsBtn.id = 'uvf-settings-btn';
4505
+ settingsBtn.setAttribute('title', 'Settings');
4506
+ settingsBtn.setAttribute('aria-label', 'Settings');
4507
+ settingsBtn.innerHTML = '<svg viewBox="0 0 24 24"><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.61 l-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.41 h-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.87 C2.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.58 c-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-0.96c0.5,0.38,1.03,0.7,1.62,0.94l0.36,2.54 c0.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.96 c0.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.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z"/></svg>';
4508
+ settingsContainer.appendChild(settingsBtn);
4509
+
4510
+ // Settings menu - will be populated dynamically based on video capabilities and configuration
4511
+ const settingsMenu = document.createElement('div');
4512
+ settingsMenu.className = 'uvf-settings-menu';
4513
+ settingsMenu.id = 'uvf-settings-menu';
4514
+ // Initially empty - will be populated by updateSettingsMenu method
4515
+ settingsMenu.innerHTML = '';
4516
+ settingsMenu.style.display = 'none';
4517
+ settingsContainer.appendChild(settingsMenu);
4518
+ rightControls.appendChild(settingsContainer);
4519
+ }
4479
4520
 
4480
4521
  // EPG button (Electronic Program Guide)
4481
4522
  const epgBtn = document.createElement('button');
@@ -5047,9 +5088,36 @@ export class WebPlayer extends BasePlayer {
5047
5088
  protected setupWatermark(): void {
5048
5089
  if (!this.watermarkCanvas) return;
5049
5090
 
5091
+ // Get watermark configuration
5092
+ const watermarkConfig = (this.config as any).watermark || {};
5093
+
5094
+ // Check if watermark is disabled
5095
+ if (watermarkConfig.enabled === false) {
5096
+ this.debugLog('Watermark disabled by configuration');
5097
+ return;
5098
+ }
5099
+
5050
5100
  const ctx = this.watermarkCanvas.getContext('2d');
5051
5101
  if (!ctx) return;
5052
5102
 
5103
+ // Default configuration values
5104
+ const config = {
5105
+ text: watermarkConfig.text || 'PREMIUM',
5106
+ showTime: watermarkConfig.showTime !== false, // default true
5107
+ updateInterval: watermarkConfig.updateInterval || 5000,
5108
+ randomPosition: watermarkConfig.randomPosition !== false, // default true
5109
+ position: watermarkConfig.position || {},
5110
+ style: {
5111
+ fontSize: watermarkConfig.style?.fontSize || 14,
5112
+ fontFamily: watermarkConfig.style?.fontFamily || 'Arial',
5113
+ opacity: watermarkConfig.style?.opacity ?? 0.3,
5114
+ color: watermarkConfig.style?.color,
5115
+ gradientColors: watermarkConfig.style?.gradientColors || ['#ff0000', '#ff4d4f']
5116
+ }
5117
+ };
5118
+
5119
+ this.debugLog('Watermark configuration:', config);
5120
+
5053
5121
  const renderWatermark = () => {
5054
5122
  const container = this.watermarkCanvas!.parentElement;
5055
5123
  if (!container) return;
@@ -5059,38 +5127,119 @@ export class WebPlayer extends BasePlayer {
5059
5127
 
5060
5128
  ctx.clearRect(0, 0, this.watermarkCanvas!.width, this.watermarkCanvas!.height);
5061
5129
 
5062
- // Gradient text effect using theme
5063
- const wrapper = this.playerWrapper as HTMLElement | null;
5064
- let c1 = '#ff0000';
5065
- let c2 = '#ff4d4f';
5066
- try {
5067
- if (wrapper) {
5068
- const styles = getComputedStyle(wrapper);
5069
- const v1 = styles.getPropertyValue('--uvf-accent-1').trim();
5070
- const v2 = styles.getPropertyValue('--uvf-accent-2').trim();
5071
- if (v1) c1 = v1;
5072
- if (v2) c2 = v2;
5073
- }
5074
- } catch (_) {}
5075
- const gradient = ctx.createLinearGradient(0, 0, 200, 0);
5076
- gradient.addColorStop(0, c1);
5077
- gradient.addColorStop(1, c2);
5130
+ // Build watermark text
5131
+ let text = config.text;
5132
+ if (config.showTime) {
5133
+ const timeStr = new Date().toLocaleTimeString();
5134
+ text += ` • ${timeStr}`;
5135
+ }
5136
+
5137
+ // Set up styling
5078
5138
  ctx.save();
5079
- ctx.globalAlpha = 0.3;
5080
- ctx.font = '14px Arial';
5081
- ctx.fillStyle = gradient;
5139
+ ctx.globalAlpha = config.style.opacity;
5140
+ ctx.font = `${config.style.fontSize}px ${config.style.fontFamily}`;
5082
5141
  ctx.textAlign = 'left';
5083
5142
 
5084
- const text = `PREMIUM • ${new Date().toLocaleTimeString()}`;
5085
- const x = 20 + Math.random() * (this.watermarkCanvas!.width - 200);
5086
- const y = 40 + Math.random() * (this.watermarkCanvas!.height - 80);
5143
+ // Set fill style
5144
+ if (config.style.color) {
5145
+ // Use solid color
5146
+ ctx.fillStyle = config.style.color;
5147
+ } else {
5148
+ // Use gradient (default or custom)
5149
+ const wrapper = this.playerWrapper as HTMLElement | null;
5150
+ let c1 = config.style.gradientColors[0];
5151
+ let c2 = config.style.gradientColors[1];
5152
+
5153
+ // Try to get theme colors if using defaults
5154
+ if (!watermarkConfig.style?.gradientColors) {
5155
+ try {
5156
+ if (wrapper) {
5157
+ const styles = getComputedStyle(wrapper);
5158
+ const v1 = styles.getPropertyValue('--uvf-accent-1').trim();
5159
+ const v2 = styles.getPropertyValue('--uvf-accent-2').trim();
5160
+ if (v1) c1 = v1;
5161
+ if (v2) c2 = v2;
5162
+ }
5163
+ } catch (_) {}
5164
+ }
5165
+
5166
+ const gradient = ctx.createLinearGradient(0, 0, 200, 0);
5167
+ gradient.addColorStop(0, c1);
5168
+ gradient.addColorStop(1, c2);
5169
+ ctx.fillStyle = gradient;
5170
+ }
5171
+
5172
+ // Calculate position
5173
+ let x: number, y: number;
5087
5174
 
5175
+ if (config.randomPosition) {
5176
+ // Random position (default behavior)
5177
+ x = 20 + Math.random() * Math.max(0, this.watermarkCanvas!.width - 200);
5178
+ y = 40 + Math.random() * Math.max(0, this.watermarkCanvas!.height - 80);
5179
+ } else {
5180
+ // Fixed or calculated position
5181
+ const posX = config.position.x;
5182
+ const posY = config.position.y;
5183
+
5184
+ // Calculate X position
5185
+ if (typeof posX === 'number') {
5186
+ x = posX;
5187
+ } else {
5188
+ switch (posX) {
5189
+ case 'left':
5190
+ x = 20;
5191
+ break;
5192
+ case 'center':
5193
+ x = this.watermarkCanvas!.width / 2;
5194
+ ctx.textAlign = 'center';
5195
+ break;
5196
+ case 'right':
5197
+ x = this.watermarkCanvas!.width - 20;
5198
+ ctx.textAlign = 'right';
5199
+ break;
5200
+ case 'random':
5201
+ x = 20 + Math.random() * Math.max(0, this.watermarkCanvas!.width - 200);
5202
+ break;
5203
+ default:
5204
+ x = 20; // default left
5205
+ }
5206
+ }
5207
+
5208
+ // Calculate Y position
5209
+ if (typeof posY === 'number') {
5210
+ y = posY;
5211
+ } else {
5212
+ switch (posY) {
5213
+ case 'top':
5214
+ y = 40;
5215
+ break;
5216
+ case 'center':
5217
+ y = this.watermarkCanvas!.height / 2;
5218
+ break;
5219
+ case 'bottom':
5220
+ y = this.watermarkCanvas!.height - 20;
5221
+ break;
5222
+ case 'random':
5223
+ y = 40 + Math.random() * Math.max(0, this.watermarkCanvas!.height - 80);
5224
+ break;
5225
+ default:
5226
+ y = 40; // default top
5227
+ }
5228
+ }
5229
+ }
5230
+
5231
+ // Render the watermark
5088
5232
  ctx.fillText(text, x, y);
5089
5233
  ctx.restore();
5234
+
5235
+ this.debugLog('Watermark rendered:', { text, x, y });
5090
5236
  };
5091
5237
 
5092
- setInterval(renderWatermark, 5000);
5093
- renderWatermark();
5238
+ // Set up interval with configured frequency
5239
+ setInterval(renderWatermark, config.updateInterval);
5240
+ renderWatermark(); // Render immediately
5241
+
5242
+ this.debugLog('Watermark setup complete with update interval:', config.updateInterval + 'ms');
5094
5243
  }
5095
5244
 
5096
5245
  public setPaywallConfig(config: any) {
@@ -5981,22 +6130,24 @@ export class WebPlayer extends BasePlayer {
5981
6130
 
5982
6131
  let menuHTML = '';
5983
6132
 
5984
- // Playback Speed Section
5985
- menuHTML += `
5986
- <div class="uvf-settings-group">
5987
- <div class="uvf-settings-label">Playback Speed</div>
5988
- <div class="uvf-settings-option speed-option" data-speed="0.25">0.25x</div>
5989
- <div class="uvf-settings-option speed-option" data-speed="0.5">0.5x</div>
5990
- <div class="uvf-settings-option speed-option" data-speed="0.75">0.75x</div>
5991
- <div class="uvf-settings-option speed-option ${this.currentPlaybackRate === 1 ? 'active' : ''}" data-speed="1">Normal</div>
5992
- <div class="uvf-settings-option speed-option" data-speed="1.25">1.25x</div>
5993
- <div class="uvf-settings-option speed-option" data-speed="1.5">1.5x</div>
5994
- <div class="uvf-settings-option speed-option" data-speed="1.75">1.75x</div>
5995
- <div class="uvf-settings-option speed-option" data-speed="2">2x</div>
5996
- </div>`;
5997
-
5998
- // Quality Section (only if qualities detected)
5999
- if (this.availableQualities.length > 0) {
6133
+ // Playback Speed Section (only if enabled in config)
6134
+ if (this.settingsConfig.speed) {
6135
+ menuHTML += `
6136
+ <div class="uvf-settings-group">
6137
+ <div class="uvf-settings-label">Playback Speed</div>
6138
+ <div class="uvf-settings-option speed-option" data-speed="0.25">0.25x</div>
6139
+ <div class="uvf-settings-option speed-option" data-speed="0.5">0.5x</div>
6140
+ <div class="uvf-settings-option speed-option" data-speed="0.75">0.75x</div>
6141
+ <div class="uvf-settings-option speed-option ${this.currentPlaybackRate === 1 ? 'active' : ''}" data-speed="1">Normal</div>
6142
+ <div class="uvf-settings-option speed-option" data-speed="1.25">1.25x</div>
6143
+ <div class="uvf-settings-option speed-option" data-speed="1.5">1.5x</div>
6144
+ <div class="uvf-settings-option speed-option" data-speed="1.75">1.75x</div>
6145
+ <div class="uvf-settings-option speed-option" data-speed="2">2x</div>
6146
+ </div>`;
6147
+ }
6148
+
6149
+ // Quality Section (only if enabled in config and qualities detected)
6150
+ if (this.settingsConfig.quality && this.availableQualities.length > 0) {
6000
6151
  menuHTML += `<div class="uvf-settings-group">
6001
6152
  <div class="uvf-settings-label">Quality</div>`;
6002
6153
 
@@ -6008,8 +6159,8 @@ export class WebPlayer extends BasePlayer {
6008
6159
  menuHTML += `</div>`;
6009
6160
  }
6010
6161
 
6011
- // Subtitles Section (only if subtitles available)
6012
- if (this.availableSubtitles.length > 0) {
6162
+ // Subtitles Section (only if enabled in config and subtitles available)
6163
+ if (this.settingsConfig.subtitles && this.availableSubtitles.length > 0) {
6013
6164
  menuHTML += `<div class="uvf-settings-group">
6014
6165
  <div class="uvf-settings-label">Subtitles/Captions</div>`;
6015
6166
 
@@ -6020,6 +6171,11 @@ export class WebPlayer extends BasePlayer {
6020
6171
 
6021
6172
  menuHTML += `</div>`;
6022
6173
  }
6174
+
6175
+ // If no sections are enabled or available, show a message
6176
+ if (!menuHTML.trim()) {
6177
+ menuHTML = '<div class="uvf-settings-group"><div class="uvf-settings-label">No settings available</div></div>';
6178
+ }
6023
6179
 
6024
6180
  settingsMenu.innerHTML = menuHTML;
6025
6181