buildtracks (player, controls) {
    this.initTracks(player);

    if (!player.tracks.length && (!player.trackFiles || !(player.trackFiles.length === 0))) {
      return;
    }

    const
      t = this,
      tracksTitle = isString(t.options.tracksText) ? t.options.tracksText : i18n.t('mejs.captions-subtitles'),
      chaptersTitle = isString(t.options.chaptersText) ? t.options.chaptersText : i18n.t('mejs.captions-chapters')
    ;

    // Hide all tracks initially (mode 'hidden' triggers loading)
    t.hideAllTracks();

    t.clearTrackHtml(player);

    player.captionsButton = document.createElement('div');
    player.captionsButton.className = `${t.options.classPrefix}button ${t.options.classPrefix}captions-button`;
    player.captionsButton.innerHTML =
      generateControlButton(t.id, tracksTitle, tracksTitle, `${t.media.options.iconSprite}`, ['icon-captions'], `${t.options.classPrefix}`) +
      `<div class="${t.options.classPrefix}captions-selector ${t.options.classPrefix}offscreen">` +
        `<ul class="${t.options.classPrefix}captions-selector-list">` +
          `<li class="${t.options.classPrefix}captions-selector-list-item">` +
            `<input type="radio" class="${t.options.classPrefix}captions-selector-input" ` +
            `name="${player.id}_captions" id="${player.id}_captions_none" ` +
            `value="none" checked disabled>` +
            `<label class="${t.options.classPrefix}captions-selector-label ` +
            `${t.options.classPrefix}captions-selected" ` +
            `for="${player.id}_captions_none">${i18n.t('mejs.none')}</label>` +
          `</li>` +
        `</ul>` +
      `</div>`;

    t.addControlElement(player.captionsButton, 'tracks');

    player.captionsButton.querySelector(`.${t.options.classPrefix}captions-selector-input`).disabled = false;

    player.chaptersButton = document.createElement('div');
    player.chaptersButton.className = `${t.options.classPrefix}button ${t.options.classPrefix}chapters-button`;
    player.chaptersButton.innerHTML =
      generateControlButton(t.id, chaptersTitle, chaptersTitle, `${t.media.options.iconSprite}`, ['icon-chapters'], `${t.options.classPrefix}`) +
      `<div class="${t.options.classPrefix}chapters-selector ${t.options.classPrefix}offscreen">` +
        `<ul class="${t.options.classPrefix}chapters-selector-list"></ul>` +
      `</div>`;

    const subtitles = t.getSubtitles();
    const chapters = t.getChapters();

    // add chapters button
    if (chapters.length > 0 && !controls.querySelector(`.${t.options.classPrefix}chapter-selector`)) {
      player.captionsButton.parentNode.insertBefore(player.chaptersButton, player.captionsButton);
    }

    // add subtitles
    for (let i = 0; i < subtitles.length; i++) {
      player.addTrackButton(subtitles[i]);
      if (subtitles[i].isLoaded) {
        // subtitles can already be loaded by the browser before UI exists, so the UI could not be updated by the load
        // event. So we enable the button, if its state show it has been loaded.
        t.enableTrackButton(subtitles[i]);
      }
    }

    player.trackToLoad = -1;
    player.selectedTrack = null;
    player.isLoadingTrack = false;

    const
      inEvents = ['mouseenter', 'focusin'],
      outEvents = ['mouseleave', 'focusout']
    ;

    // if only one language then just make the button a toggle
    if (t.options.toggleCaptionsButtonWhenOnlyOne && subtitles.length === 1) {
      player.captionsButton.classList.add(`${t.options.classPrefix}captions-button-toggle`);
      player.captionsButton.addEventListener('click', () => {
        let trackId = 'none';
        if (player.selectedTrack === null) {
          trackId = player.getSubtitles()[0].trackId;
        }
        player.setTrack(trackId);
      });
    } else {
      const
        labels = player.captionsButton.querySelectorAll(`.${t.options.classPrefix}captions-selector-label`),
        captions = player.captionsButton.querySelectorAll('input[type=radio]')
      ;

      for (let i = 0; i < inEvents.length; i++) {
        player.captionsButton.addEventListener(inEvents[i], function () {
          removeClass(this.querySelector(`.${t.options.classPrefix}captions-selector`), `${t.options.classPrefix}offscreen`);
        });
      }

      for (let i = 0; i < outEvents.length; i++) {
        player.captionsButton.addEventListener(outEvents[i], function () {
          // TODO: focusout does not work properly on mobile devices and the menu is not (visually) keyboard accessible
          setTimeout(() => {
            addClass(this.querySelector(`.${t.options.classPrefix}captions-selector`), `${t.options.classPrefix}offscreen`);
          }, 0);
        });
      }

      for (let i = 0; i < captions.length; i++) {
        captions[i].addEventListener('click',  function (e) {
          // value is trackId, same as the actual id, and we're using it here
          // because the "none" checkbox doesn't have a trackId
          // to use, but we want to know when "none" is clicked
          if (!e.target.disabled) {
            player.setTrack(this.value);
          }
        });
      }

      for (let i = 0; i < labels.length; i++) {
        labels[i].addEventListener('click',  function (e) {
          const
            radio = siblings(this, (el) => el.tagName === 'INPUT')[0],
            event = createEvent('click', radio)
          ;
          radio.dispatchEvent(event);
          e.preventDefault();
        });
      }

      //Allow up/down arrow to change the selected radio without changing the volume.
      player.captionsButton.addEventListener('keydown', (e) => {
        e.stopPropagation();
      });
    }

    for (let i = 0; i < inEvents.length; i++) {
      player.chaptersButton.addEventListener(inEvents[i], function () {
        if (this.querySelector(`.${t.options.classPrefix}chapters-selector-list`).children.length) {
          removeClass(this.querySelector(`.${t.options.classPrefix}chapters-selector`), `${t.options.classPrefix}offscreen`);
        }
      });
    }

    for (let i = 0; i < outEvents.length; i++) {
      player.chaptersButton.addEventListener(outEvents[i], function () {
        // TODO: focusout does not work properly on mobile devices and the menu is not (visually) keyboard accessible
        setTimeout(() => {
          addClass(this.querySelector(`.${t.options.classPrefix}chapters-selector`), `${t.options.classPrefix}offscreen`);
        }, 0);
      });
    }

    //Allow up/down arrow to change the selected radio without changing the volume.
    player.chaptersButton.addEventListener('keydown', (e) => {
      e.stopPropagation();
    });

    // trigger track load checks in case the browser loaded them before the UI was set up (can happen with "default")
    t.checkAllCaptionsLoadedOrError();
    t.checkAllChaptersLoadedOrError();
  }
Feature constructor.

Always has to be prefixed with `build` and the name that will be used in MepDefaults.features list
@param {MediaElementPlayer} player
@param {HTMLElement} controls