自定义Google Cast接收器陷入“正在加载”

时间:2019-10-08 13:49:21

标签: chromecast google-cast

我的自定义v3 CAF接收器应用程序正在成功播放前几场直播和点播资产。之后,由于“正在加载”,因此进入了媒体命令排队的状态。它仍然(成功)正在获取清单,但是MEDIA_STATUS仍然是“缓冲”。日志随后显示:

  

[4.537s] [cast.receiver.MediaManager]正在进行加载,媒体命令正在排队。

     

[5.893s] [cast.receiver.MediaManager]缓冲状态已更改,isPlayerBuffering:正确的旧时间:0当前时间:0

     

[5.897s] [cast.receiver.MediaManager]发送广播状态消息

     

CastContext核心事件:{“ type”:“ MEDIA_STATUS”,“ mediaStatus”:{“ mediaSessionId”:1,“ playbackRate”:1,“ playerState”:“ BUFFERING”,“ currentTime”:0,“ supportedMediaCommands” :12303,“ volume”:{“ level”:1,“ muted”:false},“ currentItemId”:1,“ repeatMode”:“ REPEAT_OFF”,“ liveSeekableRange”:{“ start”:0,“ end”: 20.000999927520752,“ isMovingWindow”:true,“ isLiveDone”:false}}}

     

CastContext MEDIA_STATUS事件:{“ type”:“ MEDIA_STATUS”,“ mediaStatus”:{“ mediaSessionId”:1,“ playbackRate”:1,“ playerState”:“ BUFFERING”,“ currentTime”:0,“ supportedMediaCommands” :12303,“ volume”:{“ level”:1,“ muted”:false},“ currentItemId”:1,“ repeatMode”:“ REPEAT_OFF”,“ liveSeekableRange”:{“ start”:0,“ end”: 20.000999927520752,“ isMovingWindow”:true,“ isLiveDone”:false}}}

     

获取完成的加载:获取“(清单网址)”。

没有显示错误。

即使在关闭并重新启动投射会话之后,问题仍然存在。投放设备本身必须重新启动才能解决。看起来数据在会话之间保留。

请务必注意,投射接收器应用尚未发布。它托管在本地网络上。

我的问题是:

  • 这种卡住的行为可能是什么原因?
  • 会话之间是否保留任何会话数据?
  • 如何完全重置投射接收器应用程序,而无需重新启动投射设备。

接收器应用程序本身非常基础。除了许可证包装外,它类似于原始示例应用程序:

const { cast } = window;

const TAG = "CastContext";

class CastStore {
  static instance = null;

  error = observable.box();

  framerate = observable.box();

  static getInstance() {
    if (!CastStore.instance) {
      CastStore.instance = new CastStore();
    }
    return CastStore.instance;
  }

  get debugLog() {
    return this.framerate.get();
  }

  get errorLog() {
    return this.error.get();
  }

  init() {
    const context = cast.framework.CastReceiverContext.getInstance();
    const playerManager = context.getPlayerManager();

    playerManager.addEventListener(
      cast.framework.events.category.CORE,
      event => {
        console.log(TAG, "Core event: " + JSON.stringify(event));
      }
    );
    playerManager.addEventListener(
      cast.framework.events.EventType.MEDIA_STATUS,
      event => {
        console.log(TAG, "MEDIA_STATUS event: " + JSON.stringify(event));
      }
    );
    playerManager.addEventListener(
      cast.framework.events.EventType.BITRATE_CHANGED,
      event => {
        console.log(TAG, "BITRATE_CHANGED event: " + JSON.stringify(event));
        runInAction(() => {
          this.framerate.set(`bitrate: ${event.totalBitrate}`);
        });
      }
    );
    playerManager.addEventListener(
      cast.framework.events.EventType.ERROR,
      event => {
        console.log(TAG, "ERROR event: " + JSON.stringify(event));
        runInAction(() => {
          this.error.set(`Error detailedErrorCode: ${event.detailedErrorCode}`);
        });
      }
    );

    // intercept the LOAD request to be able to read in a contentId and get data.
    this.loadHandler = new LoadHandler();
    playerManager.setMessageInterceptor(
      cast.framework.messages.MessageType.LOAD,
      loadRequestData => {
        this.framerate.set(null);
        this.error.set(null);

        console.log(TAG, "LOAD message: " + JSON.stringify(loadRequestData));
        if (!loadRequestData.media) {
          const error = new cast.framework.messages.ErrorData(
            cast.framework.messages.ErrorType.LOAD_CANCELLED
          );
          error.reason = cast.framework.messages.ErrorReason.INVALID_PARAM;
          return error;
        }

        if (!loadRequestData.media.entity) {
          // Copy the value from contentId for legacy reasons if needed
          loadRequestData.media.entity = loadRequestData.media.contentId;
        }

        // notify loadMedia
        this.loadHandler.onLoadMedia(loadRequestData, playerManager);
        return loadRequestData;
      }
    );

    const playbackConfig = new cast.framework.PlaybackConfig();

    // intercept license requests & responses
    playbackConfig.licenseRequestHandler = requestInfo => {
      const challenge = requestInfo.content;
      const { castToken } = this.loadHandler;
      const wrappedRequest = DrmLicenseHelper.wrapLicenseRequest(
        challenge,
        castToken
      );
      requestInfo.content = wrappedRequest;
      return requestInfo;
    };
    playbackConfig.licenseHandler = license => {
      const unwrappedLicense = DrmLicenseHelper.unwrapLicenseResponse(license);
      return unwrappedLicense;
    };

    // Duration of buffered media in seconds to start/resume playback after auto-paused due to buffering; default is 10.
    playbackConfig.autoResumeDuration = 4;

    // Minimum number of buffered segments to start/resume playback.
    playbackConfig.initialBandwidth = 1200000;

    context.start({
      touchScreenOptimizedApp: true,
      playbackConfig: playbackConfig,
      supportedCommands: cast.framework.messages.Command.ALL_BASIC_MEDIA
    });
  }
}

LoadHandler可以选择添加一个代理(我正在使用cors-anywhere代理来删除原始标头),并存储用于licenseRequests的castToken:

class LoadHandler {
  CORS_USE_PROXY = true;
  CORS_PROXY = "http://192.168.0.127:8003";

  castToken = null;

  onLoadMedia(loadRequestData, playerManager) {
    if (!loadRequestData) {
      return;
    }
    const { media } = loadRequestData;

    // disable cors for local testing
    if (this.CORS_USE_PROXY) {
      media.contentId = `${this.CORS_PROXY}/${media.contentId}`;
    }

    const { customData } = media;
    if (customData) {
      const { licenseUrl, castToken } = customData;

      // install cast token
      this.castToken = castToken;

      // handle license URL
      if (licenseUrl) {
        const playbackConfig = playerManager.getPlaybackConfig();
        playbackConfig.licenseUrl = licenseUrl;
        const { contentType } = loadRequestData.media;

        // Dash: "application/dash+xml"
        playbackConfig.protectionSystem = cast.framework.ContentProtection.WIDEVINE;

        // disable cors for local testing
        if (this.CORS_USE_PROXY) {
          playbackConfig.licenseUrl = `${this.CORS_PROXY}/${licenseUrl}`;
        }
      }
    }
  }
}

DrmHelper包装许可证请求以添加castToken并对整个数据库进行base64编码。许可证响应已通过base64解码,并在稍后展开:

export default class DrmLicenseHelper {
  static wrapLicenseRequest(challenge, castToken) {
    const wrapped = {};
    wrapped.AuthToken = castToken;
    wrapped.Payload = fromByteArray(new Uint8Array(challenge));
    const wrappedJson = JSON.stringify(wrapped);
    const wrappedLicenseRequest = fromByteArray(
      new TextEncoder().encode(wrappedJson)
    );
    return wrappedLicenseRequest;
  }

  static unwrapLicenseResponse(license) {
    try {
      const responseString = String.fromCharCode.apply(String, license);
      const responseJson = JSON.parse(responseString);
      const rawLicenseBase64 = responseJson.license;
      const decodedLicense = toByteArray(rawLicenseBase64);
      return decodedLicense;
    } catch (e) {
      return license;
    }
  }
}

2 个答案:

答案 0 :(得分:1)

cast.framework.messages.MessageType.LOAD的处理程序应始终返回:

  • (可能已修改)loadRequestData,或
  • 对(可能是经过修改的)loadRequestData的承诺
  • null放弃加载请求(我不是100%确信这适用于加载请求)

如果不执行此操作,则装入请求将保留在队列中,并且任何新请求都将在初始请求之后排队。

在处理程序中,如果!loadRequestData.media返回错误,则将使您进入该状态。另一种可能性是加载请求处理程序中的异常,这也会使您进入该状态。

答案 1 :(得分:0)

我猜我们有一种不同的方法,可以通过sendMessage发送所有可能的东西,当我们加载内容时,我们创建了一个new cast.framework.messages.LoadRequestData(),并通过playerManager.load(loadRequest)进行了加载。

但是我想您可能正在集成的Chromecast上进行测试,我们也看到了这个问题!?

我建议您做一个或多个

  • 在所有响应上启用gzip压缩!
  • 停止播放playerManager.stop()(也许在隔行间吗?)
  • 更改licenseUrl的设置方式

我们如何设置licenseUrl

playerManager.setMediaPlaybackInfoHandler((loadRequestData, playbackConfig) => {
    playbackConfig.licenseUrl = loadRequestData.customData.licenseUrl;
    return playbackConfig;
  }
);