How access PlayerAPI using bitmovin-player-react when developing (issues with hot module reload)?

Product

Player

Question

In my React (typescript, next js) app we’re using bitmovin-player-react. It seems to work well, but I’m having issues accessing the player during development

When the app is running with HMR (Hot Module Replacement), ‘React Strict Mode’ renders every component twice, causing handlePlayerRef to run twice.

It seems the PlayerAPI assigned to apiRef is destroyed, breaking my keyboard event listeners in useEffect

My question is this:

How can I access the active PlayerAPI instance when developing with HMR?

My Player Component

// ... other imports
import 'bitmovin-player/bitmovinplayer-ui.css'
import { PlayerAPI, PlayerConfig, PlayerEvent } from 'bitmovin-player'
import { PropsWithChildren, useEffect, useRef } from 'react'
import { useSourceConfig } from '../../hooks/useSourceConfig'
import '../../styles/player.scss'

export type VideoPlayerProps = PropsWithChildren<{
  videoId: string
  disabled?: boolean
}>

export default function Player(props: VideoPlayerProps) {
  // imported with require for NextJS SSR compatibility
  const BitmovinPlayer = require('bitmovin-player-react').BitmovinPlayer

  // fetches source
  const { source } = useSourceConfig({
    videoId: props.videoId,
  })
  const apiRef = useRef<PlayerAPI | null | undefined>(undefined)
  const handlePlayerRef = (player: PlayerAPI) => {
    if (!player) {
        return
    }
    apiRef.current = player

    //... then we set up controls, conviva
  }

// Keyboard Controls -- issue is here
  useEffect(() => {
    const handlePlayPause = (event: KeyboardEvent) => {
      // try to play or pause the video
      try {
        const isPaused = apiRef?.current?.isPaused()
        if (isPaused) {
          apiRef?.current?.play()
        } else {
          apiRef?.current?.pause()
        }
      } catch (error) {
        console.warn('Error pausing video', error)
      }
    }
    const handleKeyboardEvent = (event: KeyboardEvent) => {
      switch (event.key) {
        case 'Enter':
        case 'GamepadA':
          handlePlayPause(event)
          break
        default:
          break
      }
    }
    window.addEventListener('keydown', handleKeyboardEvent)
    return () => {
      window.removeEventListener('keydown', handleKeyboardEvent)
    }
  }, [])

  const config: PlayerConfig = {
    key: '[MY PLAYER LICENSE KEY]',
    cast: {
      enable: true,
    },
    playback: {
      autoplay: true,
      muted: true,
    },
    ui: false,
    events: {
      [PlayerEvent.Error]: (error: any) => {
        console.warn('Player error', error)
      },
    },
  }

  if (props?.disabled || !source) {
    return <></>
  }

  // return
  return (
    <BitmovinPlayer
      playerRef={handlePlayerRef}
      source={source}
      config={config}
      className="bitmovinplayer-wrapper"
    />
  )
}

Hello @tgrow,

bitmovin-player-react has been built with HMR and Strict mode in mind, but your case is quite specific due to Next.js and require.

What Next.js version are you using?

I suspect the issue is related to this line const BitmovinPlayer = require('bitmovin-player-react').BitmovinPlayer. Can this be wrapper with useMemo?

const BitmovinPlayer = useMemo(() => require('bitmovin-player-react').BitmovinPlayer, [])

If the above does not help, would it be possible to provide a reproducible demo?

Thanks for the reply @karpov-kir

"next": "13.1.6",

We’re using that version of next to better support our xbox uwp apps (some are still running on webview1 unfortunately).

I suspect you’re probably right. I planned on trying out memoization today. I’ll report back

Do you think upgrading next could help? would be nice to have some leverage for getting our client to accept that force-upgrading all users to webview2 is a good path forward – allowing us to upgrade next.

I’ll report back

Sounds good!

Do you think upgrading next could help?

I have very little experience with Next.js, so it’s hard to say.

Is there another way to get rid of the require inside render? Could you use e.g. dynamic? Optimizing: Lazy Loading | Next.js