PageSourceSearch

https://vildmarken.se/wp-content/plugins/wpstream/public/js/wpstream-quality-selector.js?ver=4.13.2.1787557322

js vildmarken.se collected 2026-09-26 15:23:54 UTC 10,587 bytes, 300 lines download raw bytes

1/**
2 * WpStream quality selector for Video.js (VHS/HLS).
3 *
4 * Avoids external selector plugins that can break on Video.js 8.
5 * Requires: videojs-contrib-quality-levels
6 */
7// IIFE keeps every helper and component class out of the global scope; only the
8// installer (window.wpstreamInstallQualitySelector) is exported at the end.
9(function (window) {
10  'use strict';
11
12  // Nothing to do if Video.js was not loaded on this page.
13  if (!window.videojs) {
14    return;
15  }
16
17  // Local handle to the global Video.js factory.
18  const videojs = window.videojs;
19
20  /**
21   * Detect whether a player is backed by VHS (HTTP streaming / HLS-MPD tech).
22   *
23   * @param {Object} player Video.js player instance.
24   * @return {boolean} True when the player exposes qualityLevels().
25   */
26  function isHlsPlayer(player) {
27    try {
28      // VHS exposes qualityLevels() on the player when HLS/MPD tech is used
29      return typeof player.qualityLevels === 'function';
30    } catch (e) {
31      // Any access error means we cannot treat this as an HLS player.
32      return false;
33    }
34  }
35
36  /**
37   * Read the current quality levels off a player as a plain array.
38   *
39   * @param {Object} player Video.js player instance.
40   * @return {Array} Snapshot of quality-level objects (empty on failure).
41   */
42  function getLevels(player) {
43    try {
44      // The QualityLevelList is array-like but not a real Array.
45      const ql = player.qualityLevels();
46      if (!ql) return [];
47      // Copy each indexed level into a real array we can slice/sort/iterate.
48      const arr = [];
49      for (let i = 0; i < ql.length; i++) arr.push(ql[i]);
50      return arr;
51    } catch (e) {
52      // Player disposed or plugin missing: behave as if there are no levels.
53      return [];
54    }
55  }
56
57  /**
58   * Build a human-readable label for a single quality level.
59   *
60   * @param {Object} level A quality-level object (may have height/width/bitrate).
61   * @return {string} Label such as "720p", "1280x720", "800 kbps", or fallback.
62   */
63  function prettyLabel(level) {
64    if (!level) return '';
65    // Prefer resolution height, then explicit dimensions, then bitrate.
66    const h = level.height;
67    const w = level.width;
68    const br = level.bitrate;
69    if (h) return `${h}p`;                                 // e.g. "720p"
70    if (w && level.height) return `${w}x${level.height}`;  // e.g. "1280x720"
71    if (br) return `${Math.round(br / 1000)} kbps`;        // e.g. "800 kbps"
72    // Nothing useful to show: generic fallback label.
73    return 'Quality';
74  }
75
76  /**
77   * Enable every quality level (used by the "Auto" choice so VHS is free to pick).
78   *
79   * @param {Array} levels Quality-level objects to enable.
80   */
81  function enableAll(levels) {
82    levels.forEach((l) => {
83      try {
84        // Turning all levels on hands adaptive selection back to VHS.
85        l.enabled = true;
86      } catch (e) {}  // ignore levels that reject the assignment
87    });
88  }
89
90  /**
91   * Restrict playback to a single chosen level, disabling all others.
92   *
93   * @param {Array} levels Quality-level objects to toggle.
94   * @param {Object} selected The one level that should stay enabled.
95   */
96  function enableOnly(levels, selected) {
97    levels.forEach((l) => {
98      try {
99        // Only the selected level stays enabled; everything else is off.
100        l.enabled = l === selected;
101      } catch (e) {}  // ignore levels that reject the assignment
102    });
103  }
104
105  // Base Video.js components we extend to build the quality control.
106  const MenuButton = videojs.getComponent('MenuButton');
107  const MenuItem = videojs.getComponent('MenuItem');
108
109  /**
110   * A single clickable row in the quality menu (either "Auto" or one level).
111   */
112  class WpstreamQualityMenuItem extends MenuItem {
113    /**
114     * @param {Object} player  Owning Video.js player.
115     * @param {Object} options Item config: label, wpstreamLevel, wpstreamIsAuto, selected.
116     */
117    constructor(player, options) {
118      super(player, options);
119      // The concrete quality level this row selects (null for the Auto row).
120      this.wpstreamLevel = options.wpstreamLevel || null;
121      // Flag marking this row as the adaptive "Auto" choice.
122      this.wpstreamIsAuto = !!options.wpstreamIsAuto;
123      // Screen-reader / control text for the row.
124      this.controlText(options.label || '');
125      // Styling hook for the menu item.
126      this.addClass('vjs-wpstream-quality-item');
127      // Reflect whether this row is the currently active choice.
128      this.selected(!!options.selected);
129    }
130
131    /**
132     * Apply this row's quality choice when the user clicks it.
133     */
134    handleClick() {
135      const player = this.player();
136      // Re-read levels fresh; the list can change as segments load.
137      const levels = getLevels(player);
138
139      // Auto row: re-enable every level and let VHS adapt.
140      if (this.wpstreamIsAuto) {
141        enableAll(levels);
142        try {
143          // Notify the menu button so it rebuilds/repaints the selection.
144          player.trigger('wpstreamqualitychange');
145        } catch (e) {}
146        return;
147      }
148
149      // Specific level row: lock playback to just that level.
150      if (this.wpstreamLevel) {
151        enableOnly(levels, this.wpstreamLevel);
152        try {
153          // Notify the menu button so it rebuilds/repaints the selection.
154          player.trigger('wpstreamqualitychange');
155        } catch (e) {}
156      }
157    }
158  }
159
160  /**
161   * The control-bar button that opens the quality menu and keeps it in sync.
162   */
163  class WpstreamQualityMenuButton extends MenuButton {
164    /**
165     * @param {Object} player  Owning Video.js player.
166     * @param {Object} options Standard Video.js component options.
167     */
168    constructor(player, options) {
169      super(player, options);
170      // Styling hook + accessible label for the button.
171      this.addClass('vjs-wpstream-quality');
172      this.controlText('Quality');
173
174      // Rebuild menu when quality levels change.
175      // Metadata/data load and our custom event all warrant a rebuild.
176      player.on('loadedmetadata', () => this.update());
177      player.on('loadeddata', () => this.update());
178      player.on('wpstreamqualitychange', () => this.update());
179
180      try {
181        // Also react to VHS's own quality-level list mutations, if available.
182        const ql = player.qualityLevels && player.qualityLevels();
183        if (ql && typeof ql.on === 'function') {
184          ql.on('addqualitylevel', () => this.update());     // new rendition appeared
185          ql.on('removequalitylevel', () => this.update());  // rendition dropped
186          ql.on('change', () => this.update());              // active rendition switched
187        }
188      } catch (e) {}  // quality-levels plugin absent: static menu is fine
189    }
190
191    /**
192     * Build the list of menu rows (Auto + one row per distinct quality level).
193     *
194     * @return {Array} Array of WpstreamQualityMenuItem instances.
195     */
196    createItems() {
197      const player = this.player();
198      const levels = getLevels(player);
199
200      // Always show the menu button, even if there's only one level.
201      // This is useful for debugging / confirming the stream is being interpreted as HLS.
202      try {
203        this.show();
204      } catch (e) {}
205
206      // Sort a copy highest-quality first: by height, then bitrate as tiebreak.
207      const sorted = levels
208        .slice()
209        .sort((a, b) => (b.height || 0) - (a.height || 0) || (b.bitrate || 0) - (a.bitrate || 0));
210
211      // Accumulates the menu rows we will return.
212      const items = [];
213
214      // Auto
215      // First row is always "Auto"; selected when every level is currently enabled.
216      items.push(
217        new WpstreamQualityMenuItem(player, {
218          label: 'Auto',
219          wpstreamIsAuto: true,
220          selectable: true,
221          selected: sorted.length > 0 ? sorted.every((l) => l.enabled === true) : true,
222        })
223      );
224
225      // If VHS hasn't populated levels yet, keep Auto only (menu still visible).
226      if (sorted.length === 0) {
227        return items;
228      }
229
230      // Dedupe by label (common case: duplicate heights)
231      // Set tracks labels already added so we don't list the same resolution twice.
232      const seen = new Set();
233      sorted.forEach((level) => {
234        const label = prettyLabel(level);
235        if (!label) return;            // skip levels with no usable label
236        if (seen.has(label)) return;   // skip duplicates of an already-listed label
237        seen.add(label);
238
239        // One row per distinct level; selected only when it is the sole enabled level.
240        items.push(
241          new WpstreamQualityMenuItem(player, {
242            label,
243            wpstreamLevel: level,
244            selectable: true,
245            selected: level.enabled === true && sorted.filter((l) => l.enabled === true).length === 1,
246          })
247        );
248      });
249
250      return items;
251    }
252  }
253
254  // Register the button so it can be added to a player's control bar by name.
255  videojs.registerComponent('WpstreamQualityMenuButton', WpstreamQualityMenuButton);
256
257  /**
258   * Install the quality menu on a player.
259   *
260   * @param {Object} player Video.js player to attach the quality control to.
261   */
262  function install(player) {
263    // Skip missing or already-disposed players.
264    if (!player || player.isDisposed && player.isDisposed()) return;
265
266    // Ensure we only install once.
267    // Guard flag prevents adding a second button on repeat calls.
268    if (player.wpstreamQualitySelectorInitialized) return;
269    player.wpstreamQualitySelectorInitialized = true;
270
271    // Only for VHS/HLS players.
272    if (!isHlsPlayer(player)) return;
273
274    // Defer until the player is ready so its control bar exists.
275    player.ready(function () {
276      try {
277        // Locate the control bar we will inject the button into.
278        const cb = player.getChild('controlBar');
279        if (!cb) return;
280
281        // Avoid duplicates.
282        if (cb.getChild('WpstreamQualityMenuButton')) return;
283
284        // Place near the right side (before fullscreen if available)
285        // Insert two slots from the end so it sits just before the fullscreen toggle.
286        const children = cb.children();
287        let insertIndex = children ? children.length - 2 : undefined;
288        if (insertIndex < 0) insertIndex = undefined;  // fall back to default position
289
290        cb.addChild('WpstreamQualityMenuButton', {}, insertIndex);
291      } catch (e) {
292        // ignore
293      }
294    });
295  }
296
297  // Expose the installer so player bootstrap code can call it per player.
298  window.wpstreamInstallQualitySelector = install;
299})(window);
300

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.