text: /*! @license
   * Shaka Player
   * Copyright 2016 Google LLC
   * SPDX-License-Identifier: Apache-2.0
   */
  
  
  goog.provide('shakaDemo.Config');
  
  
  /** @type {?shakaDemo.Config} */
  let shakaDemoConfig;
  
  
  /**
   * Shaka Player demo, configuration page layout.
   */
  shakaDemo.Config = class {
    /**
     * Register the page configuration.
     */
    static init() {
      const container = shakaDemoMain.getHamburgerMenu();
      shakaDemoConfig = new shakaDemo.Config(container);
    }
  
    /** @param {!Element} container */
    constructor(container) {
      /** @private {!Element} */
      this.container_ = container;
  
      /**
       * A list of all sections.
       * @private {!Array.<!shakaDemo.InputContainer>}
       */
      this.sections_ = [];
  
      /**
       * The input object for the control currently being constructed.
       * @private {?shakaDemo.Input}
       */
      this.latestInput_ = null;
  
      this.reload_();
  
      // Listen to external config changes (i.e. from hash changes).
      document.addEventListener('shaka-main-config-change', () => {
        // Respond to them by remaking. This is to avoid triggering any config
        // changes based on the config changes.
        this.reloadAndSaveState_();
      });
      document.addEventListener('shaka-main-locale-changed', () => {
        // Respond to them by remaking. This is to avoid triggering any config
        // changes based on the config changes.
        this.reloadAndSaveState_();
      });
      document.addEventListener('shaka-main-drawer-state-change', () => {
        this.setContentAvailability_(shakaDemoMain.getIsDrawerOpen());
      });
      this.setContentAvailability_(shakaDemoMain.getIsDrawerOpen());
    }
  
    /**
     * @param {boolean} availability
     * @private
     */
    setContentAvailability_(availability) {
      if (availability) {
        this.container_.classList.remove('hidden');
      } else {
        this.container_.classList.add('hidden');
      }
    }
  
    /** @private */
    reload_() {
      shaka.util.Dom.removeAllChildren(this.container_);
      this.sections_ = [];
  
      this.addMetaSection_();
      this.addLanguageSection_();
      this.addAbrSection_();
      this.addOfflineSection_();
      this.addDrmSection_();
      this.addStreamingSection_();
      this.addManifestSection_();
      this.addRetrictionsSection_('',
          shakaDemo.MessageIds.RESTRICTIONS_SECTION_HEADER);
    }
  
    /**
     * Remake the contents of the div. Unlike |reload_|, this will also remember
     * which sections were open.
     * @private
     */
    reloadAndSaveState_() {
      const wasOpenArray = this.sections_.map((section) => section.getIsOpen());
      this.reload_();
      for (let i = 0; i < wasOpenArray.length; i++) {
        const wasOpen = wasOpenArray[i];
        const section = this.sections_[i];
        if (wasOpen) {
          section.open();
        }
      }
  
      // Update the componentHandler, to account for any new MDL elements added.
      componentHandler.upgradeDom();
    }
  
    /** @return {!shaka.extern.AdvancedDrmConfiguration} */
    static emptyAdvancedConfiguration() {
      return {
        distinctiveIdentifierRequired: false,
        persistentStateRequired: false,
        videoRobustness: '',
        audioRobustness: '',
        serverCertificate: new Uint8Array(0),
        individualizationServer: '',
      };
    }
  
    /** @private */
    addDrmSection_() {
      const MessageIds = shakaDemo.MessageIds;
      const docLink = this.resolveExternLink_('.DrmConfiguration');
      this.addSection_(MessageIds.DRM_SECTION_HEADER, docLink)
          .addBoolInput_(MessageIds.DELAY_LICENSE,
              'drm.delayLicenseRequestUntilPlayed')
          .addBoolInput_(MessageIds.LOG_LICENSE_EXCHANGE,
              'drm.logLicenseExchange')
          .addNumberInput_(MessageIds.UPDATE_EXPIRATION_TIME,
              'drm.updateExpirationTime',
              /* canBeDecimal= */ true,
              /* canBeZero= */ false,
              /* canBeUnset= */ true);
      const advanced = shakaDemoMain.getConfiguration().drm.advanced || {};
      const robustnessSuggestions = [
        'SW_SECURE_CRYPTO',
        'SW_SECURE_DECODE',
        'HW_SECURE_CRYPTO',
        'HW_SECURE_DECODE',
        'HW_SECURE_ALL',
      ];
      const addRobustnessField = (name, valueName) => {
        // All robustness fields of a given type are set at once.
        this.addDatalistInput_(name, robustnessSuggestions, (input) => {
          // Add in any common drmSystem not currently in advanced.
          for (const drmSystem of shakaDemo.Main.commonDrmSystems) {
            if (!(drmSystem in advanced)) {
              advanced[drmSystem] = shakaDemo.Config.emptyAdvancedConfiguration();
            }
          }
          // Set the robustness.
          for (const drmSystem in advanced) {
            advanced[drmSystem][valueName] = input.value;
          }
          shakaDemoMain.configure('drm.advanced', advanced);
          shakaDemoMain.remakeHash();
        });
        const keySystem = Object.keys(advanced)[0];
        if (keySystem) {
          const currentRobustness = advanced[keySystem][valueName];
          this.latestInput_.input().value = currentRobustness;
        }
      };
      addRobustnessField(MessageIds.VIDEO_ROBUSTNESS, 'videoRobustness');
      addRobustnessField(MessageIds.AUDIO_ROBUSTNESS, 'audioRobustness');
      this.addRetrySection_('drm', MessageIds.DRM_RETRY_SECTION_HEADER);
    }
  
    /** @private */
    addManifestSection_() {
      const MessageIds = shakaDemo.MessageIds;
      const docLink = this.resolveExternLink_('.ManifestConfiguration');
      this.addSection_(MessageIds.MANIFEST_SECTION_HEADER, docLink)
          .addBoolInput_(MessageIds.IGNORE_DASH_DRM,
              'manifest.dash.ignoreDrmInfo')
          .addBoolInput_(MessageIds.AUTO_CORRECT_DASH_DRIFT,
              'manifest.dash.autoCorrectDrift')
          .addBoolInput_(MessageIds.XLINK_FAIL_GRACEFULLY,
              'manifest.dash.xlinkFailGracefully')
          .addBoolInput_(MessageIds.IGNORE_DASH_SUGGESTED_PRESENTATION_DELAY,
              'manifest.dash.ignoreSuggestedPresentationDelay')
          .addBoolInput_(MessageIds.IGNORE_DASH_EMPTY_ADAPTATION_SET,
              'manifest.dash.ignoreEmptyAdaptationSet')
          .addBoolInput_(MessageIds.IGNORE_HLS_TEXT_FAILURES,
              'manifest.hls.ignoreTextStreamFailures')
          .addBoolInput_(MessageIds.USE_FULL_SEGMENTS_FOR_START_TIME,
              'manifest.hls.useFullSegmentsForStartTime')
          .addNumberInput_(MessageIds.AVAILABILITY_WINDOW_OVERRIDE,
              'manifest.availabilityWindowOverride',
              /* canBeDecimal= */ true,
              /* canBeZero= */ false,
              /* canBeUnset= */ true)
          .addTextInput_(MessageIds.CLOCK_SYNC_URI, 'manifest.dash.clockSyncUri')
          .addBoolInput_(MessageIds.IGNORE_DRM, 'manifest.dash.ignoreDrmInfo')
          .addNumberInput_(MessageIds.DEFAULT_PRESENTATION_DELAY,
              'manifest.defaultPresentationDelay')
          .addBoolInput_(MessageIds.IGNORE_MIN_BUFFER_TIME,
              'manifest.dash.ignoreMinBufferTime')
          .addNumberInput_(MessageIds.INITIAL_SEGMENT_LIMIT,
              'manifest.dash.initialSegmentLimit',
              /* canBeDecimal= */ false,
              /* canBeZero= */ false,
              /* canBeUnset= */ true)
          .addBoolInput_(MessageIds.DISABLE_AUDIO,
              'manifest.disableAudio')
          .addBoolInput_(MessageIds.DISABLE_VIDEO,
              'manifest.disableVideo')
          .addBoolInput_(MessageIds.DISABLE_TEXT,
              'manifest.disableText')
          .addBoolInput_(MessageIds.LOW_LATENCY,
              'manifest.lowLatencyMode');
  
      this.addRetrySection_('manifest', MessageIds.MANIFEST_RETRY_SECTION_HEADER);
    }
  
    /** @private */
    addAbrSection_() {
      const MessageIds = shakaDemo.MessageIds;
      const docLink = this.resolveExternLink_('.AbrConfiguration');
      this.addSection_(MessageIds.ADAPTATION_SECTION_HEADER, docLink)
          .addBoolInput_(MessageIds.ENABLED, 'abr.enabled')
          .addBoolInput_(MessageIds.NETWORK_INFORMATION,
              'abr.useNetworkInformation')
          .addNumberInput_(MessageIds.BANDWIDTH_ESTIMATE,
              'abr.defaultBandwidthEstimate')
          .addNumberInput_(MessageIds.BANDWIDTH_DOWNGRADE,
              'abr.bandwidthDowngradeTarget',
              /* canBeDecimal= */ true)
          .addNumberInput_(MessageIds.BANDWIDTH_UPGRADE,
              'abr.bandwidthUpgradeTarget',
              /* canBeDecimal= */ true)
          .addNumberInput_(MessageIds.SWITCH_INTERVAL,
              'abr.switchInterval',
              /* canBeDecimal= */ true);
      this.addRetrictionsSection_('abr',
          MessageIds.ADAPTATION_RESTRICTIONS_SECTION_HEADER);
    }
  
    /**
     * @param {string} category
     * @param {!shakaDemo.MessageIds} sectionName
     * @private
     */
    addRetrictionsSection_(category, sectionName) {
      const MessageIds = shakaDemo.MessageIds;
      const prefix = (category ? category + '.' : '') + 'restrictions.';
      const docLink = this.resolveExternLink_('.Restrictions');
      this.addSection_(sectionName, docLink)
          .addNumberInput_(MessageIds.MIN_WIDTH, prefix + 'minWidth')
          .addNumberInput_(MessageIds.MAX_WIDTH, prefix + 'maxWidth')
          .addNumberInput_(MessageIds.MIN_HEIGHT, prefix + 'minHeight')
          .addNumberInput_(MessageIds.MAX_HEIGHT, prefix + 'maxHeight')
          .addNumberInput_(MessageIds.MIN_PIXELS, prefix + 'minPixels')
          .addNumberInput_(MessageIds.MAX_PIXELS, prefix + 'maxPixels')
          .addNumberInput_(MessageIds.MIN_FRAMERATE, prefix + 'minFrameRate')
          .addNumberInput_(MessageIds.MAX_FRAMERATE, prefix + 'maxFrameRate')
          .addNumberInput_(MessageIds.MIN_BANDWIDTH, prefix + 'minBandwidth')
          .addNumberInput_(MessageIds.MAX_BANDWIDTH, prefix + 'maxBandwidth');
    }
  
    /**
     * @param {string} category
     * @param {!shakaDemo.MessageIds} sectionName
     * @private
     */
    addRetrySection_(category, sectionName) {
      const MessageIds = shakaDemo.MessageIds;
      const prefix = category + '.retryParameters.';
      const docLink = this.resolveExternLink_('.RetryParameters');
      this.addSection_(sectionName, docLink)
          .addNumberInput_(MessageIds.MAX_ATTEMPTS, prefix + 'maxAttempts')
          .addNumberInput_(MessageIds.BASE_DELAY, prefix + 'baseDelay',
              /* canBeDecimal= */ true)
          .addNumberInput_(MessageIds.BACKOFF_FACTOR, prefix + 'backoffFactor',
              /* canBeDecimal= */ true)
          .addNumberInput_(MessageIds.FUZZ_FACTOR, prefix + 'fuzzFactor',
              /* canBeDecimal= */ true)
          .addNumberInput_(MessageIds.TIMEOUT, prefix + 'timeout',
              /* canBeDecimal= */ true);
    }
  
    /** @private */
    addOfflineSection_() {
      const MessageIds = shakaDemo.MessageIds;
      const docLink = this.resolveExternLink_('.OfflineConfiguration');
      this.addSection_(MessageIds.OFFLINE_SECTION_HEADER, docLink)
          .addBoolInput_(MessageIds.USE_PERSISTENT_LICENSES,
              'offline.usePersistentLicense');
    }
  
    /** @private */
    addStreamingSection_() {
      const MessageIds = shakaDemo.MessageIds;
      const docLink = this.resolveExternLink_('.StreamingConfiguration');
      this.addSection_(MessageIds.STREAMING_SECTION_HEADER, docLink)
          .addNumberInput_(MessageIds.MAX_SMALL_GAP_SIZE,
              'streaming.smallGapLimit',
              /* canBeDecimal= */ true)
          .addNumberInput_(MessageIds.BUFFERING_GOAL,
              'streaming.bufferingGoal',
              /* canBeDecimal= */ true)
          .addNumberInput_(MessageIds.DURATION_BACKOFF,
              'streaming.durationBackoff',
              /* canBeDecimal= */ true)
          .addNumberInput_(MessageIds.REBUFFERING_GOAL,
              'streaming.rebufferingGoal',
              /* canBeDecimal= */ true)
          .addNumberInput_(MessageIds.BUFFER_BEHIND,
              'streaming.bufferBehind',
              /* canBeDecimal= */ true)
          .addNumberInput_(MessageIds.SAFE_SEEK_OFFSET,
              'streaming.safeSeekOffset',
              /* canBeDecimal= */ true)
          .addNumberInput_(MessageIds.STALL_THRESHOLD,
              'streaming.stallThreshold',
              /* canBeDecimal= */ true)
          .addNumberInput_(MessageIds.SAFE_SKIP_DISTANCE,
              'streaming.stallSkip',
              /* canBeDecimal= */ true)
          .addNumberInput_(MessageIds.INACCURATE_MANIFEST_TOLERANCE,
              'streaming.inaccurateManifestTolerance',
              /* canBeDecimal= */ true);
  
      if (!shakaDemoMain.getNativeControlsEnabled()) {
        this.addBoolInput_(MessageIds.ALWAYS_STREAM_TEXT,
            'streaming.alwaysStreamText');
      } else {
        // Add a fake custom fixed "input" that warns the users not to change it.
        const noop = (input) => {};
        this.addCustomBoolInput_(MessageIds.ALWAYS_STREAM_TEXT,
            noop, MessageIds.ALWAYS_STREAM_TEXT_WARNING);
        this.latestInput_.input().disabled = true;
        this.latestInput_.input().checked = true;
      }
  
      this.addBoolInput_(MessageIds.JUMP_LARGE_GAPS,
          'streaming.jumpLargeGaps')
          .addBoolInput_(MessageIds.FORCE_TRANSMUX_TS,
              'streaming.forceTransmuxTS')
          .addBoolInput_(MessageIds.START_AT_SEGMENT_BOUNDARY,
              'streaming.startAtSegmentBoundary')
          .addBoolInput_(MessageIds.IGNORE_TEXT_FAILURES,
              'streaming.ignoreTextStreamFailures')
          .addBoolInput_(MessageIds.STALL_DETECTOR_ENABLED,
              'streaming.stallEnabled')
          .addBoolInput_(MessageIds.USE_NATIVE_HLS_SAFARI,
              'streaming.useNativeHlsOnSafari');
      this.addRetrySection_('streaming',
          MessageIds.STREAMING_RETRY_SECTION_HEADER);
    }
  
    /** @private */
    addLanguageSection_() {
      const MessageIds = shakaDemo.MessageIds;
      const docLink = this.resolveExternLink_('.PlayerConfiguration');
      this.addSection_(MessageIds.LANGUAGE_SECTION_HEADER, docLink)
          .addTextInput_(MessageIds.AUDIO_LANGUAGE, 'preferredAudioLanguage')
          .addTextInput_(MessageIds.TEXT_LANGUAGE, 'preferredTextLanguage');
      const onChange = (input) => {
        shakaDemoMain.setUILocale(input.value);
        shakaDemoMain.remakeHash();
      };
      this.addCustomTextInput_(MessageIds.UI_LOCALE, onChange);
      this.latestInput_.input().value = shakaDemoMain.getUILocale();
      this.addNumberInput_(MessageIds.AUDIO_CHANNEL_COUNT,
          'preferredAudioChannelCount');
    }
  
    /** @private */
    addMetaSection_() {
      const MessageIds = shakaDemo.MessageIds;
      this.addSection_(/* name= */ null, /* docLink= */ null);
  
      this.addCustomBoolInput_(MessageIds.SHAKA_CONTROLS, (input) => {
        shakaDemoMain.setNativeControlsEnabled(!input.checked);
        if (input.checked) {
          // Forcibly set |streaming.alwaysStreamText| to true.
          shakaDemoMain.configure('streaming.alwaysStreamText', true);
          shakaDemoMain.remakeHash();
        }
        // Enabling/disabling Shaka Controls will change how other controls in
        // the config work, so reload the page.
        this.reloadAndSaveState_();
      });
      // TODO: Re-add the tooltipMessage of 'Takes effect next load.' once we
      // are ready to add ALL of the tooltip messages.
      if (!shakaDemoMain.getNativeControlsEnabled()) {
        this.latestInput_.input().checked = true;
      }
  
      if (!shakaDemoMain.getNativeControlsEnabled()) {
        this.addCustomBoolInput_(MessageIds.TRICK_PLAY_CONTROLS, (input) => {
          shakaDemoMain.setTrickPlayControlsEnabled(input.checked);
        });
        if (shakaDemoMain.getTrickPlayControlsEnabled()) {
          this.latestInput_.input().checked = true;
        }
      } else {
        // Add a fake custom fixed "input" that warns the users not to change it.
        const noop = (input) => {};
        this.addCustomBoolInput_(MessageIds.TRICK_PLAY_CONTROLS,
            noop, MessageIds.TRICK_PLAY_CONTROLS_WARNING);
        this.latestInput_.input().disabled = true;
        this.latestInput_.input().checked = false;
      }
  
      // shaka.log is not set if logging isn't enabled.
      // I.E. if using the release version of shaka.
      if (!shaka['log']) {
        return;
      }
  
      // Access shaka.log using bracket syntax because shaka.log is not exported.
      // Exporting the logging methods proved to be a bad solution, both in terms
      // of difficulty and in terms of what changes it would require of the
      // architectural design of Shaka Player, so this non-type-safe solution is
      // the best remaining way to get the Closure compiler to compile this
      // method.
      const Level = shaka['log']['Level'];
      const setLevel = shaka['log']['setLevel'];
  
      const localize = (name) => shakaDemoMain.getLocalizedString(name);
      const logLevels = {
        'info': localize(MessageIds.LOG_LEVEL_INFO),
        'debug': localize(MessageIds.LOG_LEVEL_DEBUG),
        'v': localize(MessageIds.LOG_LEVEL_V),
        'vv': localize(MessageIds.LOG_LEVEL_VV),
      };
      const onChange = (input) => {
        switch (input.value) {
          case 'info':
            setLevel(Level['INFO']);
            break;
          case 'debug':
            setLevel(Level['DEBUG']);
            break;
          case 'vv':
            setLevel(Level['V2']);
            break;
          case 'v':
            setLevel(Level['V1']);
            break;
        }
        shakaDemoMain.remakeHash();
      };
      this.addSelectInput_(MessageIds.LOG_LEVEL, logLevels, onChange);
      const input = this.latestInput_.input();
      switch (shaka['log']['currentLevel']) {
        case Level['DEBUG']:
          input.value = 'debug';
          break;
        case Level['V2']:
          input.value = 'vv';
          break;
        case Level['V1']:
          input.value = 'v';
          break;
        default:
          input.value = 'info';
          break;
      }
    }
  
    /**
     * @param {string} suffix
     * @return {string}
     * @private
     */
    resolveExternLink_(suffix) {
      return '../docs/api/shaka.extern.html#' + suffix;
    }
  
    /**
     * @param {?shakaDemo.MessageIds} name
     * @param {?string} docLink
     * @return {!shakaDemo.Config}
     * @private
     */
    addSection_(name, docLink) {
      const style = name ?
                    shakaDemo.InputContainer.Style.ACCORDION :
                    shakaDemo.InputContainer.Style.VERTICAL;
      this.sections_.push(new shakaDemo.InputContainer(
          this.container_, name, style, docLink));
  
      return this;
    }
  
    /**
     * @param {!shakaDemo.MessageIds} name
     * @param {string} valueName
     * @param {shakaDemo.MessageIds=} tooltipMessage
     * @return {!shakaDemo.Config}
     * @private
     */
    addBoolInput_(name, valueName, tooltipMessage) {
      const onChange = (input) => {
        shakaDemoMain.configure(valueName, input.checked);
        shakaDemoMain.remakeHash();
      };
      this.addCustomBoolInput_(name, onChange, tooltipMessage);
      if (shakaDemoMain.getCurrentConfigValue(valueName)) {
        this.latestInput_.input().checked = true;
      }
      return this;
    }
  
    /**
     * @param {!shakaDemo.MessageIds} name
     * @param {function(!HTMLInputElement)} onChange
     * @param {shakaDemo.MessageIds=} tooltipMessage
     * @return {!shakaDemo.Config}
     * @private
     */
    addCustomBoolInput_(name, onChange, tooltipMessage) {
      this.createRow_(name, tooltipMessage);
      const localized = shakaDemoMain.getLocalizedString(name);
      this.latestInput_ = new shakaDemo.BoolInput(
          this.getLatestSection_(), localized, onChange);
      return this;
    }
  
    /**
     * @param {!shakaDemo.MessageIds} name
     * @param {string} valueName
     * @param {shakaDemo.MessageIds=} tooltipMessage
     * @return {!shakaDemo.Config}
     * @private
     */
    addTextInput_(name, valueName, tooltipMessage) {
      const onChange = (input) => {
        shakaDemoMain.configure(valueName, input.value);
        shakaDemoMain.remakeHash();
      };
      this.addCustomTextInput_(name, onChange, tooltipMessage);
      this.latestInput_.input().value =
          shakaDemoMain.getCurrentConfigValue(valueName);
      return this;
    }
  
    /**
     * @param {!shakaDemo.MessageIds} name
     * @param {function(!HTMLInputElement)} onChange
     * @param {shakaDemo.MessageIds=} tooltipMessage
     * @return {!shakaDemo.Config}
     * @private
     */
    addCustomTextInput_(name, onChange, tooltipMessage) {
      this.createRow_(name, tooltipMessage);
      const localized = shakaDemoMain.getLocalizedString(name);
      this.latestInput_ = new shakaDemo.TextInput(
          this.getLatestSection_(), localized, onChange);
      return this;
    }
  
    /**
     * @param {!shakaDemo.MessageIds} name
     * @param {string} valueName
     * @param {boolean=} canBeDecimal
     * @param {boolean=} canBeZero
     * @param {boolean=} canBeUnset
     * @param {shakaDemo.MessageIds=} tooltipMessage
     * @return {!shakaDemo.Config}
     * @private
     */
    addNumberInput_(name, valueName, canBeDecimal = false, canBeZero = true,
        canBeUnset = false, tooltipMessage) {
      const onChange = (input) => {
        shakaDemoMain.resetConfiguration(valueName);
        shakaDemoMain.remakeHash();
        if (input.value == 'Infinity') {
          shakaDemoMain.configure(valueName, Infinity);
          shakaDemoMain.remakeHash();
          return;
        }
        if (input.value == '' && canBeUnset) {
          return;
        }
        const valueAsNumber = Number(input.value);
        if (valueAsNumber == 0 && !canBeZero) {
          return;
        }
        if (!isNaN(valueAsNumber)) {
          if (Math.floor(valueAsNumber) != valueAsNumber && !canBeDecimal) {
            return;
          }
          shakaDemoMain.configure(valueName, valueAsNumber);
          shakaDemoMain.remakeHash();
        }
      };
      this.createRow_(name, tooltipMessage);
      const localized = shakaDemoMain.getLocalizedString(name);
      this.latestInput_ = new shakaDemo.NumberInput(
          this.getLatestSection_(), localized, onChange, canBeDecimal, canBeZero,
          canBeUnset);
      this.latestInput_.input().value =
          shakaDemoMain.getCurrentConfigValue(valueName);
      if (isNaN(Number(this.latestInput_.input().value)) && canBeUnset) {
        this.latestInput_.input().value = '';
      }
      return this;
    }
  
    /**
     * @param {!shakaDemo.MessageIds} name
     * @param {!Array.<string>} values
     * @param {function(!HTMLInputElement)} onChange
     * @param {shakaDemo.MessageIds=} tooltipMessage
     * @return {!shakaDemo.Config}
     * @private
     */
    addDatalistInput_(name, values, onChange, tooltipMessage) {
      this.createRow_(name, tooltipMessage);
      const localized = shakaDemoMain.getLocalizedString(name);
      this.latestInput_ = new shakaDemo.DatalistInput(
          this.getLatestSection_(), localized, onChange, values);
      return this;
    }
  
    /**
     * @param {!shakaDemo.MessageIds} name
     * @param {!Object.<string, string>} values
     * @param {function(!HTMLInputElement)} onChange
     * @param {shakaDemo.MessageIds=} tooltipMessage
     * @return {!shakaDemo.Config}
     * @private
     */
    addSelectInput_(name, values, onChange, tooltipMessage) {
      this.createRow_(name, tooltipMessage);
      // The input is not provided a name, as (in this enclosed space) it makes
      // the actual field unreadable.
      this.latestInput_ = new shakaDemo.SelectInput(
          this.getLatestSection_(), null, onChange, values);
      return this;
    }
  
    /**
     * @param {!shakaDemo.MessageIds} name
     * @param {shakaDemo.MessageIds=} tooltipMessage
     * @private
     */
    createRow_(name, tooltipMessage) {
      this.getLatestSection_().addRow(name, tooltipMessage || null);
    }
  
    /**
     * Gets the latest section. Results in a failed assert if there is no latest
     * section.
     * @return {!shakaDemo.InputContainer}
     * @private
     */
    getLatestSection_() {
      goog.asserts.assert(this.sections_.length > 0,
          'Must have at least one section.');
      return this.sections_[this.sections_.length - 1];
    }
  };
  
  
  document.addEventListener('shaka-main-loaded', shakaDemo.Config.init);
  document.addEventListener('shaka-main-cleanup', () => {
    shakaDemoConfig = null;
  });
  