Пользовательский приемник Google Cast застрял в "Идет загрузка" - PullRequest
1 голос
/ 08 октября 2019

Мое собственное приложение ресивера v3 CAF успешно воспроизводит первые несколько живых и видео активов. После этого он переходит в состояние, когда команды мультимедиа ставятся в очередь из-за «Идет загрузка». Это все еще (успешно) извлекает манифесты, но MEDIA_STATUS остается "буферизировать". Журнал затем показывает:

[4.537s] [cast.receiver.MediaManager] Загрузка выполняется, команда мультимедиа находится в очереди.

[5.893s] [cast.receiver.MediaManager] Состояние буферизации изменено, isPlayerBuffering: истина, старое время: 0 текущее время: 0

[5.897s] [cast.receiver.MediaManager] Отправка сообщения о состоянии широковещания

Событие ядра CastContext: {"Тип": "MEDIA_STATUS", "mediaStatus": { "mediaSessionId": 1, "playbackRate": 1, "playerState": "БУФЕРИЗАЦИЯ", "CURRENTTIME": 0, "supportedMediaCommands": 12303, "объем": {"уровень": 1, "приглушен" ложь}, "currentItemId": 1, "repeatMode": "REPEAT_OFF", "liveSeekableRange": { "старт": 0, "конец": +20,000999927520752, "isMovingWindow": правда,"isLiveDone": false}}}

Событие CastContext MEDIA_STATUS: {"type": "MEDIA_STATUS", "mediaStatus": {"mediaSessionId": 1, «PlayRate": 1, «playerState»: «BUFFERING»», "CURRENTTIME": 0, "supportedMediaCommands": 12303, "объем": { "уровень": 1, "приглушен" ложь}, "currentItemId": 1, "repeatMode": "REPEAT_OFF", "liveSeekableRange":{ "СТОrt ": 0," end ": 20.000999927520752," isMovingWindow ": true," isLiveDone ": false}}}

Получить законченную загрузку: GET" (url манифеста) ".

Ошибки не отображаются.

Даже после закрытия и перезапуска сеанса приведения проблема остается. Само устройство приведения должно быть перезагружено для его разрешения. Похоже, что данные хранятся между сессиями.

Важно отметить, что приложение для приёмника еще не опубликовано. Он размещен в локальной сети.

Мои вопросы:

  • В чем может быть причина этого зависшего поведения?
  • Есть ли какие-либо данные сеанса междусеанс?
  • Как полностью сбросить приложение приёмника, без необходимости перезапуска устройства приведения.

Само приложение приёмника очень простое. В отличие от переноса лицензии, он напоминает пример приложения vanilla:

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 -where для удаления заголовка источника) и сохраняет castToken для licenseRequests:

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 ]

1 голос
/ 17 октября 2019

Обработчик для cast.framework.messages.MessageType.LOAD всегда должен возвращать:

  • (возможно, измененный) loadRequestData или
  • обещание (возможно, измененное) loadRequestData
  • null для отклонения запроса на загрузку (я не уверен на 100%, что это работает для запросов на загрузку)

Если вы этого не сделаете, запрос на загрузку останется в очередии любой новый запрос ставится в очередь после первоначального.

В вашем обработчике вы возвращаете ошибку, если !loadRequestData.media, что приведет вас в это состояние. Другая возможность - исключение в обработчике запроса загрузки, который также приведет вас в это состояние.

0 голосов
/ 01 ноября 2019

Полагаю, у нас другой подход и мы посылаем все возможное через 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;
  }
);
...