IBM / video-streaming-web-player-api

This is the public Web Player API for controlling embedded players created at video.ibm.com.
Apache License 2.0
5 stars 6 forks source link

Console error when loading PlayerAPI in React #10

Open stephenhamm opened 2 years ago

stephenhamm commented 2 years ago

My team is having an issue with the Player API loading in React 16. This console error is showing whenever the player is loaded.

Screen Shot 2021-09-22 at 9 43 28 AM

Recently someone from my team created an issue here (https://github.com/IBM/video-streaming-web-player-api/issues/6) and it was resolved. This ticket saw the introduction of the 'api-target-origin' parameter for the player's iframe. While my colleague was able to get this to work in Angular, the issue is still persisting in React.

The Player API is imported into our component

Screen Shot 2021-09-22 at 9 48 25 AM

The api-target-origin is included in the source, as well as the rootUrl (which in our case is http://localhost:3000).

Screen Shot 2021-09-22 at 9 48 55 AM

The iframe's source is set.

Screen Shot 2021-09-22 at 9 49 53 AM

The request being sent to Watson is as follows, which includes the api-target-origin. https://video.ibm.com/embed/recorded/130908580?html5ui&api-target-origin=http://localhost:3000&showtitle=true&allowfullscreen=false&volume=50

In my colleagues notes in the previous ticket, the 'html5ui=' parameter comes after the 'api-target-origin' param, so even trying this as a hardcoded value still produces the error.

Screen Shot 2021-09-22 at 9 53 18 AM

He mentions that the error happens when the SSO dialogue window is present, but i'm seeing the error in the console even when already signed in.

I also noticed that there are two requests sent, one coming back with a 301 and another with 200.

The full file contents are as follows.

import PlayerAPI from 'ibm-video-streaming-web-player-api';
import React, { useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';

type WMPlayerErrorEvent = { name: 'autoplayRejected'; message: string };

type Props = {
  id: string;
  src?: string;
  thumbnail?: string;
  pauseVideo?: boolean;
  seekToTimestamp?: number;
  onVideoProgress?: (progressInSec: number) => void;
  onVideoPlayClicked?: (isPlaying: boolean) => void;
};

const T_NS = 'MediaPlayer';

const MediaPlayer = (props: Props): JSX.Element => {
  const { id, src, thumbnail, pauseVideo, seekToTimestamp, onVideoProgress, onVideoPlayClicked } =
    props;
  const { t } = useTranslation(T_NS);

  const playerRef = useRef<HTMLIFrameElement>(null);

  const rootUrl = encodeURIComponent(window.location.origin);
  const srcUpdated = src
    ? `${src}&api-target-origin=${rootUrl}&showtitle=true&allowfullscreen=false&volume=50`
    : '';

  const [playerContentAvailable, setPlayerContentAvailable] = useState(false);
  const [videoIsPlaying, setVideoIsPlaying] = useState(false);
  const playerSourceAvailable = !!(srcUpdated && srcUpdated.length > 0);
  const playerReady = playerRef && !!playerRef.current;

  // add media player handlers
  useEffect(() => {
    try {
      if (!playerSourceAvailable || !playerReady) {
        return;
      }

      const onError = (event: WMPlayerErrorEvent): void => {
        const { name, message } = event;

        switch (name) {
          case 'autoplayRejected':
            throw new Error(message);
        }
      };

      const onContentAvailable = (data: string): void => {
        if (data === 'contentAvailable') {
          setPlayerContentAvailable(true);
        }
      };

      const onPlaying = (viewer: any, isPlaying: boolean): void => {
        setVideoIsPlaying(isPlaying);

        if (onVideoPlayClicked) {
          onVideoPlayClicked(isPlaying);
        }

        viewer.getProperty('progress', (progressInSec: number) => {
          if (!isPlaying && onVideoProgress) {
            onVideoProgress(progressInSec);
          }
        });
      };

      const onSeekStarted = (data: {
        from: number;
        to: number;
        initiator: 'user' | 'system';
      }): void => {
        const { to: progressInSec } = data;
        if (onVideoProgress) {
          onVideoProgress(progressInSec);
        }
      };

      const viewer = PlayerAPI(playerRef.current?.id);

      if (videoIsPlaying && pauseVideo) {
        viewer.callMethod('pause');
      } else if (!videoIsPlaying && !pauseVideo) {
        viewer.callMethod('play');
      }

      viewer.addListener('error', onError);

      viewer.addListener('contentAvailable', onContentAvailable);

      viewer.addListener('playing', (type: 'playing', isPlaying: boolean) =>
        onPlaying(viewer, isPlaying),
      );

      viewer.addListener(
        'seekStarted',
        (type: 'seekStarted', data: { from: number; to: number; initiator: 'user' | 'system' }) =>
          onSeekStarted(data),
      );

      return () => {
        viewer.removeListener('error', onError);
        viewer.removeListener('contentAvailable', onContentAvailable);
        viewer.removeListener('playing', onPlaying);
        viewer.removeListener('seekStarted', onSeekStarted);
      };
    } catch (error) {
      throw new Error(error.message);
    }
    /* eslint-disable react-hooks/exhaustive-deps */
  }, [id, onVideoProgress, playerSourceAvailable, playerReady, pauseVideo]);

  // on edit comment the player seeks to time of comment and pauses.
  useEffect(() => {
    if (!playerSourceAvailable || !playerReady || !seekToTimestamp) {
      return;
    }

    const viewer = PlayerAPI(playerRef.current?.id);
    viewer.callMethod('seek', seekToTimestamp);
    viewer.callMethod('pause');
  }, [playerSourceAvailable, playerReady, seekToTimestamp]);

  return (
    <div className='media-player'>
      {!playerSourceAvailable && (
        <div className='container'>
          <div className='image-container'>
            <img
              className='img'
              src={thumbnail}
              alt={t('Video content is not available for playback at this moment')}
            />
          </div>
          <div className='play-container'>
            {t('Video content is not available for playback at this moment')}
          </div>
        </div>
      )}
      {playerSourceAvailable && !playerContentAvailable && (
        <div className='loading-video'>{t('Loading video')}</div>
      )}
      {playerSourceAvailable && (
        <iframe
          ref={playerRef}
          id={id}
          title={id}
          src={srcUpdated}
          allowFullScreen={false}
          frameBorder='0'
          sandbox='allow-forms allow-popups allow-same-origin allow-scripts allow-top-navigation-by-user-activation'
          width='100%'
          height='100%'
        />
      )}
    </div>
  );
};

export default MediaPlayer;

Any further help with this would be very much appreciated. Thanks!

jungdaniel commented 2 years ago

Hi @stephenhamm, Sorry for the late response and thank you for the detailed description. I started working on this, I let you know if I have something.

jungdaniel commented 2 years ago

👋 Hi again,

I put together a little project with create-react-app based on your use-case here: https://github.com/jungdaniel/ibm-video-streaming-web-player-api-issue-10

Apart from seeing that error message does the api work? I couldn't consistently reproduce that error message, but when I saw it, the api still worked for me after the sso process finished. I tested it in chrome and firefox, both on localhost:3000 and on gh-pages. We are executing some code when the videos' iframe loads and in case of SSO it can happen a few times. I suspect that the error happens when the postMessage is called during the SSO.

Here you can check the example in action: https://jungdaniel.github.io/ibm-video-streaming-web-player-api-issue-10/

pooshonbanerjee commented 2 years ago

Yes @jungdaniel , the API works even when that message shows up. And you are also right when you say that the message shows up sometimes, and sometimes doesn't. The reason why we opened this ticket is because this application where we are using this SDK is a production level application, to be used company-wide. So we are trying to avoid these errors showing up on this console. Do you think something can be done about this?

jungdaniel commented 2 years ago

Yes, I see your point. I guess we can wrap the postMessage call in a try catch block to suppress this "false positive" error. I'll discuss it with the team, maybe we can find something even better.

pooshonbanerjee commented 2 years ago

Thanks a lot @jungdaniel for looking into this and possibly closing in on a resolution.

pooshonbanerjee commented 2 years ago

Hi @jungdaniel , any luck with this?

pooshonbanerjee commented 2 years ago

Hi @jungdaniel , any update?

jungdaniel commented 2 years ago

Hi @pooshonbanerjee, It seems that with a try catch block we won't be able to suppress this kind of message. I am looking for another solution right now.

pooshonbanerjee commented 2 years ago

Thanks Jung.

Regards Pooshon Banerjee

On Wed, 13 Oct, 2021, 3:17 pm Jung Dániel, @.***> wrote:

Hi @pooshonbanerjee https://github.com/pooshonbanerjee, It seems that with a try catch block we won't be able to suppress this kind of message. I am looking for another solution right now.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/IBM/video-streaming-web-player-api/issues/10#issuecomment-942122822, or unsubscribe https://github.com/notifications/unsubscribe-auth/AJTJE6CLLH4XV57OJPAK5KDUGVISJANCNFSM5ERISKJA . Triage notifications on the go with GitHub Mobile for iOS https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Android https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub.

jungdaniel commented 2 years ago

@pooshonbanerjee @stephenhamm,

Unfortunatelly we couldn't find a solution for this issue. I couldn't catch these errors neither with try catch nor with window.onerror. The only thing I found, suggested to delay the postMessage call with setTimeout and clear the timeout if another load event is triggered on the iframe. Seems like a hack and we would rather not rely on timers in this case.

stephenhamm commented 2 years ago

Thanks @jungdaniel for the reply. Would this timeout be built-in on your end or is it something we have to implement?

jungdaniel commented 2 years ago

Would be built in this lib.

pooshonbanerjee commented 2 years ago

Thanks @jungdaniel

pooshonbanerjee commented 2 years ago

Hi @jungdaniel , any idea when this new update will be released?

pooshonbanerjee commented 2 years ago

Hi @jungdaniel , any update on this?