Source: lib/device/webos.js

/*! @license
 * Shaka Player
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

goog.provide('shaka.device.WebOS');

goog.require('shaka.config.CrossBoundaryStrategy');
goog.require('shaka.device.AbstractDevice');
goog.require('shaka.device.DeviceFactory');
goog.require('shaka.device.IDevice');
goog.require('shaka.log');


/**
 * @final
 */
shaka.device.WebOS = class extends shaka.device.AbstractDevice {
  constructor() {
    super();

    /** @private {?number} */
    this.osVersion_ = this.guessWebOSVersion_();

    /** @private {?boolean} */
    this.supportHdr_ = null;

    try {
      const bridge = new PalmServiceBridge();
      bridge.onservicecallback = (n) => {
        shaka.log.info(n);
        const configsJSON =
        /** @type {shaka.device.WebOS.PalmServiceBridgeResponse} */ (
            JSON.parse(n));
        this.supportHdr_ = configsJSON.configs['tv.model.supportHDR'] ||
            configsJSON.configs['tv.config.supportDolbyHDRContents'] || false;
      };
      const configs = {
        configNames: [
          'tv.model.supportHDR',
          'tv.config.supportDolbyHDRContents',
        ],
      };
      // eslint-disable-next-line no-restricted-syntax
      bridge.call('luna://com.webos.service.config/getConfigs',
          JSON.stringify(configs));
    } catch (e) {
      shaka.log.alwaysWarn('WebOS: getConfigs call failed', e);
      // Ignore errors.
    }
  }

  /**
   * @override
   */
  getVersion() {
    return this.osVersion_;
  }

  /**
   * @override
   */
  getDeviceName() {
    return 'WebOS';
  }

  /**
   * @override
   */
  getDeviceType() {
    return shaka.device.IDevice.DeviceType.TV;
  }

  /**
   * @override
   */
  getBrowserEngine() {
    return shaka.device.IDevice.BrowserEngine.CHROMIUM;
  }

  /**
   * @override
   */
  supportsMediaCapabilities() {
    return false;
  }

  /**
   * @override
   */
  supportsSequenceMode() {
    const version = this.getVersion();
    return version !== null ? version > 3 : super.supportsSequenceMode();
  }

  /**
   * @override
   */
  supportsSmoothCodecSwitching() {
    const version = this.getVersion();
    return version !== null ?
        version > 6 : super.supportsSmoothCodecSwitching();
  }

  /**
   * @override
   */
  supportsServerCertificate() {
    const version = this.getVersion();
    return version !== null ? version > 3 : super.supportsServerCertificate();
  }

  /**
   * @override
   */
  detectMaxHardwareResolution() {
    const maxResolution = {width: 1920, height: 1080};
    try {
      const deviceInfo =
      /** @type {{screenWidth: number, screenHeight: number}} */(
          JSON.parse(window.PalmSystem.deviceInfo));
      // WebOS has always been able to do 1080p.  Assume a 1080p limit.
      maxResolution.width = Math.max(1920, deviceInfo['screenWidth']);
      maxResolution.height = Math.max(1080, deviceInfo['screenHeight']);
    } catch (e) {
      shaka.log.alwaysWarn('WebOS: Error detecting screen size, default ' +
          'screen size 1920x1080.');
    }

    return Promise.resolve(maxResolution);
  }

  /**
   * @override
   */
  adjustConfig(config) {
    super.adjustConfig(config);

    if (this.getVersion() === 3) {
      config.streaming.crossBoundaryStrategy =
          shaka.config.CrossBoundaryStrategy.RESET;
    }
    config.streaming.shouldFixTimestampOffset = true;
    // WebOS has long hardware pipeline that respond slowly to seeking.
    // Therefore we should not seek when we detect a stall on this platform.
    // Instead, default stallSkip to 0 to force the stall detector to pause
    // and play instead.
    config.streaming.stallSkip = 0;
    return config;
  }

  /**
   * @override
   */
  getHdrLevel(preferHLG) {
    if (this.supportHdr_ == null) {
      shaka.log.alwaysWarn('WebOS: getConfigs call haven\'t finished');
      return super.getHdrLevel(preferHLG);
    }
    if (this.supportHdr_) {
      // It relies on previous codec filtering
      return preferHLG ? 'HLG' : 'PQ';
    }
    return 'SDR';
  }

  /**
   * @return {?number}
   * @private
   */
  guessWebOSVersion_() {
    let browserVersion = null;
    const match = navigator.userAgent.match(/Chrome\/(\d+)/);
    if (match) {
      browserVersion = parseInt(match[1], /* base= */ 10);
    }

    switch (browserVersion) {
      case 38:
        return 3;
      case 53:
        return 4;
      case 68:
        return 5;
      case 79:
        return 6;
      case 87:
        return 22;
      case 94:
        return 23;
      case 108:
        return 24;
      case 120:
        return 25;
      default:
        return null;
    }
  }

  /**
   * @override
   */
  supportStandardVP9Checking() {
    return false;
  }

  /**
   * @return {boolean}
   * @private
   */
  static isWebOS_() {
    return navigator.userAgent.includes('Web0S');
  }
};


/**
 * @typedef {{
 *   configs: Object
 * }}
 *
 * @property {Object} configs
 */
shaka.device.WebOS.PalmServiceBridgeResponse;

if (shaka.device.WebOS.isWebOS_()) {
  const webOSDevice = new shaka.device.WebOS();
  shaka.device.DeviceFactory.registerDeviceFactory(
      () => webOSDevice);
}