From 2c1584a347f22f628a46f27e63dd7ab612800490 Mon Sep 17 00:00:00 2001 From: dananji Date: Mon, 11 Sep 2023 13:42:06 -0400 Subject: [PATCH 1/2] Add i18n support with language optin in VideoJS --- src/components/MediaPlayer/MediaPlayer.js | 1 + .../MediaPlayer/VideoJS/VideoJSPlayer.js | 16 +++++++++------- .../MediaPlayer/VideoJS/VideoJSUtils.js | 15 +++++++++++++++ 3 files changed, 25 insertions(+), 7 deletions(-) create mode 100644 src/components/MediaPlayer/VideoJS/VideoJSUtils.js diff --git a/src/components/MediaPlayer/MediaPlayer.js b/src/components/MediaPlayer/MediaPlayer.js index a986b346..a5dc6966 100644 --- a/src/components/MediaPlayer/MediaPlayer.js +++ b/src/components/MediaPlayer/MediaPlayer.js @@ -210,6 +210,7 @@ const MediaPlayer = ({ enableFileDownload = false, enablePIP = false }) => { poster: isVideo ? getPoster(manifest, canvasIndex) : null, controls: true, fluid: true, + language: "fr", // TODO:: fill this information from props controlBar: { // Define and order control bar controls // See https://docs.videojs.com/tutorial-components.html for options of what diff --git a/src/components/MediaPlayer/VideoJS/VideoJSPlayer.js b/src/components/MediaPlayer/VideoJS/VideoJSPlayer.js index 75d5a905..4b8a69ae 100644 --- a/src/components/MediaPlayer/VideoJS/VideoJSPlayer.js +++ b/src/components/MediaPlayer/VideoJS/VideoJSPlayer.js @@ -2,7 +2,6 @@ import React from 'react'; import PropTypes from 'prop-types'; import videojs from 'video.js'; import 'videojs-hotkeys'; - import 'videojs-markers-plugin/dist/videojs-markers-plugin'; import 'videojs-markers-plugin/dist/videojs.markers.plugin.css'; @@ -26,12 +25,8 @@ import { } from '@Services/iiif-parser'; import { checkSrcRange, getMediaFragment } from '@Services/utility-helpers'; -import VideoJSProgress from './components/js/VideoJSProgress'; -import VideoJSCurrentTime from './components/js/VideoJSCurrentTime'; -import VideoJSFileDownload from './components/js/VideoJSFileDownload'; -import VideoJSNextButton from './components/js/VideoJSNextButton'; -import VideoJSPreviousButton from './components/js/VideoJSPreviousButton'; -// import vjsYo from './vjsYo'; +import { LANG_MAP } from './VideoJSUtils'; + function VideoJSPlayer({ isVideo, @@ -102,11 +97,18 @@ function VideoJSPlayer({ setCIndex(canvasIndex); + // Load desired language from VideoJS's lang files + let languageJSON = LANG_MAP[options.language]; let newPlayer; if (playerRef.current != null) { + languageJSON + ? videojs.addLanguage(options.language, languageJSON) + /** When desired language is not available defaults to English */ + : videojs.addLanguage("en", LANG_MAP["en"]); newPlayer = videojs(playerRef.current, options); } + /* Another way to add a component to the controlBar */ // newPlayer.getChild('controlBar').addChild('vjsYo', {}); diff --git a/src/components/MediaPlayer/VideoJS/VideoJSUtils.js b/src/components/MediaPlayer/VideoJS/VideoJSUtils.js new file mode 100644 index 00000000..3868e482 --- /dev/null +++ b/src/components/MediaPlayer/VideoJS/VideoJSUtils.js @@ -0,0 +1,15 @@ +/** VideoJS custom components */ +import VideoJSProgress from './components/js/VideoJSProgress'; +import VideoJSCurrentTime from './components/js/VideoJSCurrentTime'; +import VideoJSFileDownload from './components/js/VideoJSFileDownload'; +import VideoJSNextButton from './components/js/VideoJSNextButton'; +import VideoJSPreviousButton from './components/js/VideoJSPreviousButton'; +// import vjsYo from './vjsYo'; + +/** VideoJS language files */ +import de from "video.js/dist/lang/de.json"; +import en from "video.js/dist/lang/en.json"; +import fr from "video.js/dist/lang/fr.json"; +import it from "video.js/dist/lang/it.json"; + +export const LANG_MAP = { "de": de, "fr": fr, "it": it, "en": en }; From 669d945a6a00ece96ed5e96c4d2995c5f55d615f Mon Sep 17 00:00:00 2001 From: dananji Date: Thu, 14 Sep 2023 10:50:06 -0400 Subject: [PATCH 2/2] Fixes from code review: dynamically import VideoJS locale JSON --- src/components/MediaPlayer/MediaPlayer.js | 2 +- .../MediaPlayer/MediaPlayer.test.js | 22 ++++++++--- .../MediaPlayer/VideoJS/VideoJSPlayer.js | 39 ++++++++++++++----- .../MediaPlayer/VideoJS/VideoJSUtils.js | 15 ------- 4 files changed, 47 insertions(+), 31 deletions(-) delete mode 100644 src/components/MediaPlayer/VideoJS/VideoJSUtils.js diff --git a/src/components/MediaPlayer/MediaPlayer.js b/src/components/MediaPlayer/MediaPlayer.js index a5dc6966..8509b146 100644 --- a/src/components/MediaPlayer/MediaPlayer.js +++ b/src/components/MediaPlayer/MediaPlayer.js @@ -210,7 +210,7 @@ const MediaPlayer = ({ enableFileDownload = false, enablePIP = false }) => { poster: isVideo ? getPoster(manifest, canvasIndex) : null, controls: true, fluid: true, - language: "fr", // TODO:: fill this information from props + language: "en", // TODO:: fill this information from props controlBar: { // Define and order control bar controls // See https://docs.videojs.com/tutorial-components.html for options of what diff --git a/src/components/MediaPlayer/MediaPlayer.test.js b/src/components/MediaPlayer/MediaPlayer.test.js index 82f92ffd..d026e702 100644 --- a/src/components/MediaPlayer/MediaPlayer.test.js +++ b/src/components/MediaPlayer/MediaPlayer.test.js @@ -1,5 +1,5 @@ import React from 'react'; -import { render, screen, waitFor } from '@testing-library/react'; +import { act, render, screen, waitFor } from '@testing-library/react'; import { withManifestAndPlayerProvider } from '../../services/testing-helpers'; import MediaPlayer from './MediaPlayer'; import audioManifest from '@TestData/transcript-canvas'; @@ -10,6 +10,16 @@ let manifestState = { playlist: { isPlaylist: false, markers: [], isEditing: false } }; describe('MediaPlayer component', () => { + let originalError; + beforeEach(() => { + originalError = console.error; + console.error = jest.fn(); + }); + + afterAll(() => { + console.error = originalError; + }); + describe('with audio manifest', () => { beforeEach(() => { const PlayerWithManifest = withManifestAndPlayerProvider(MediaPlayer, { @@ -67,7 +77,7 @@ describe('MediaPlayer component', () => { initialPlayerState: {}, enableFileDownload: true, }); - render(); + await act(async () => render()); expect(screen.queryByTestId('videojs-file-download')).toBeInTheDocument(); }); }); @@ -96,12 +106,12 @@ describe('MediaPlayer component', () => { }); describe('with multiple canvases', () => { - test('renders previous/next section buttons', () => { + test('renders previous/next section buttons', async () => { const PlayerWithManifest = withManifestAndPlayerProvider(MediaPlayer, { initialManifestState: { ...manifestState, manifest: videoManifest, canvasIndex: 0 }, initialPlayerState: {}, }); - render(); + await act(async () => render()); expect(screen.queryByTestId('videojs-next-button')).toBeInTheDocument(); expect(screen.queryByTestId('videojs-previous-button')).toBeInTheDocument(); }); @@ -126,7 +136,7 @@ describe('MediaPlayer component', () => { expect(screen.getByText('You do not have permission to playback this item.')).toBeInTheDocument(); }); - test('renders player for a accessible Canvas', () => { + test('renders player for a accessible Canvas', async () => { const PlayerWithManifest = withManifestAndPlayerProvider(MediaPlayer, { initialManifestState: { manifest: playlistManifest, @@ -135,7 +145,7 @@ describe('MediaPlayer component', () => { }, initialPlayerState: {}, }); - render(); + await act(async () => render()); expect(screen.queryByTestId('inaccessible-item')).not.toBeInTheDocument(); expect( screen.queryAllByTestId('videojs-video-element').length diff --git a/src/components/MediaPlayer/VideoJS/VideoJSPlayer.js b/src/components/MediaPlayer/VideoJS/VideoJSPlayer.js index 4b8a69ae..6d169e45 100644 --- a/src/components/MediaPlayer/VideoJS/VideoJSPlayer.js +++ b/src/components/MediaPlayer/VideoJS/VideoJSPlayer.js @@ -25,8 +25,13 @@ import { } from '@Services/iiif-parser'; import { checkSrcRange, getMediaFragment } from '@Services/utility-helpers'; -import { LANG_MAP } from './VideoJSUtils'; - +/** VideoJS custom components */ +import VideoJSProgress from './components/js/VideoJSProgress'; +import VideoJSCurrentTime from './components/js/VideoJSCurrentTime'; +import VideoJSFileDownload from './components/js/VideoJSFileDownload'; +import VideoJSNextButton from './components/js/VideoJSNextButton'; +import VideoJSPreviousButton from './components/js/VideoJSPreviousButton'; +// import vjsYo from './vjsYo'; function VideoJSPlayer({ isVideo, @@ -86,25 +91,41 @@ function VideoJSPlayer({ let currentNavItemRef = React.useRef(); currentNavItemRef.current = currentNavItem; + // Using dynamic imports to enforce code-splitting in webpack + // https://webpack.js.org/api/module-methods/#dynamic-expressions-in-import + const loadResources = async (langKey) => { + try { + const resources = await import(`video.js/dist/lang/${langKey}.json`); + return resources; + } catch (e) { + console.error(`${langKey} is not available, defaulting to English`); + const resources = await import('video.js/dist/lang/en.json'); + return resources; + } + }; + /** * Initialize player when creating for the first time and cleanup * when unmounting after the player is being used */ - React.useEffect(() => { + React.useEffect(async () => { const options = { ...videoJSOptions, }; setCIndex(canvasIndex); - // Load desired language from VideoJS's lang files - let languageJSON = LANG_MAP[options.language]; + // Dynamically load the selected language from VideoJS's lang files + let selectedLang; + await loadResources(options.language) + .then((res) => { + selectedLang = JSON.stringify(res); + }); + let languageJSON = JSON.parse(selectedLang); + let newPlayer; if (playerRef.current != null) { - languageJSON - ? videojs.addLanguage(options.language, languageJSON) - /** When desired language is not available defaults to English */ - : videojs.addLanguage("en", LANG_MAP["en"]); + videojs.addLanguage(options.language, languageJSON); newPlayer = videojs(playerRef.current, options); } diff --git a/src/components/MediaPlayer/VideoJS/VideoJSUtils.js b/src/components/MediaPlayer/VideoJS/VideoJSUtils.js deleted file mode 100644 index 3868e482..00000000 --- a/src/components/MediaPlayer/VideoJS/VideoJSUtils.js +++ /dev/null @@ -1,15 +0,0 @@ -/** VideoJS custom components */ -import VideoJSProgress from './components/js/VideoJSProgress'; -import VideoJSCurrentTime from './components/js/VideoJSCurrentTime'; -import VideoJSFileDownload from './components/js/VideoJSFileDownload'; -import VideoJSNextButton from './components/js/VideoJSNextButton'; -import VideoJSPreviousButton from './components/js/VideoJSPreviousButton'; -// import vjsYo from './vjsYo'; - -/** VideoJS language files */ -import de from "video.js/dist/lang/de.json"; -import en from "video.js/dist/lang/en.json"; -import fr from "video.js/dist/lang/fr.json"; -import it from "video.js/dist/lang/it.json"; - -export const LANG_MAP = { "de": de, "fr": fr, "it": it, "en": en };