videojs/video.js · error · Error

Invalid listener for ${objName(obj)}#${fnName}; must be a fu

Error message

Invalid listener for ${objName(obj)}#${fnName}; must be a function.

What it means

The listener passed to on/one/off must be a function. Video.js invokes listeners via the underlying DOM Events system, so a non-callable value would throw at dispatch time in a less helpful way; this guard fails early at registration. The error message includes the host object and method for context.

Source

Thrown at src/js/mixins/evented.js:143

/**
 * Validates a value to determine if it is a valid listener. Throws if not.
 *
 * @private
 * @throws {Error}
 *         If the listener is not a function.
 *
 * @param  {Function} listener
 *         The listener to test.
 *
 * @param  {Object} obj
 *         The evented object we are validating for
 *
 * @param  {string} fnName
 *         The name of the evented mixin function that called this.
 */
const validateListener = (listener, obj, fnName) => {
  if (typeof listener !== 'function') {
    throw new Error(`Invalid listener for ${objName(obj)}#${fnName}; must be a function.`);
  }
};

/**
 * Takes an array of arguments given to `on()` or `one()`, validates them, and
 * normalizes them into an object.
 *
 * @private
 * @param  {Object} self
 *         The evented object on which `on()` or `one()` was called. This
 *         object will be bound as the `this` value for the listener.
 *
 * @param  {Array} args
 *         An array of arguments passed to `on()` or `one()`.
 *
 * @param  {string} fnName
 *         The name of the evented mixin function that called this.
 *

View on GitHub (pinned to c3a7e0e6d2)

Solutions

  1. Pass an actual function reference: obj.on('click', this.handleClick.bind(this)).
  2. Use an arrow function to preserve this: obj.on('click', (e) => this.handleClick(e)).
  3. Verify the imported/exported handler exists before binding.

Example fix

// before
player.on('play', this.handlePlay); // handlePlay undefined
// after
player.on('play', (e) => this.handlePlay(e));
Defensive patterns

Strategy: type-guard

Validate before calling

function bindListener(fn, ctx) {
  if (typeof fn !== 'function') throw new TypeError('listener must be a function');
  return arguments.length > 1 ? fn.bind(ctx) : fn;
}

Type guard

const isListener = (v) => typeof v === 'function';

Prevention

When it happens

Trigger: Calling obj.on('click', undefined), obj.on('click', null), obj.on('click', {handleEvent(){}}) (object not function), or obj.on('click', someMethod) where someMethod was not bound/exported.

Common situations: Forgetting to bind a method; passing a property name instead of the function reference; arrow-function vs method binding confusion; importing a named export that does not exist.

Related errors


AI-assisted analysis of videojs/video.js@c3a7e0e6d2 (2026-08-13). Data as JSON: /api/errors/ec1fe9f4ea12b352. Report an issue: GitHub.