onMessageReceived_(namespace, serialized) {
    // Since this method is in the compiled library, make sure all messages
    // passed in here were created with quoted property names.

    const message = shaka.cast.CastUtils.deserialize(serialized);
    shaka.log.v2('CastSender: message', message);

    switch (message['type']) {
      case 'event': {
        const targetName = message['targetName'];
        const event = message['event'];
        const fakeEvent = shaka.util.FakeEvent.fromRealEvent(event);
        this.onRemoteEvent_(targetName, fakeEvent);
        break;
      }
      case 'update': {
        const update = message['update'];
        for (const targetName in update) {
          const target = this.cachedProperties_[targetName] || {};
          for (const property in update[targetName]) {
            target[property] = update[targetName][property];
          }
        }
        if (this.hasJoinedExistingSession_) {
          this.onFirstCastStateUpdate_();
          this.hasJoinedExistingSession_ = false;
        }
        break;
      }
      case 'asyncComplete': {
        const id = message['id'];
        const error = message['error'];
        const res = message['res'];
        const p = this.asyncCallPromises_.get(id);
        this.asyncCallPromises_.delete(id);

        goog.asserts.assert(p, 'Unexpected async id');
        if (!p) {
          break;
        }

        if (error) {
          // This is a hacky way to reconstruct the serialized error.
          const reconstructedError = new shaka.util.Error(
              error.severity, error.category, error.code);
          for (const k in error) {
            (/** @type {Object} */(reconstructedError))[k] = error[k];
          }
          p.reject(reconstructedError);
        } else {
          p.resolve(res);
        }
        break;
      }
    }
  }
@param {string} namespace
@param {string} serialized
@private