From 499ccb53a0a9f2ce41b336e8d0a6787d41afefb7 Mon Sep 17 00:00:00 2001 From: Tom <20648924+moT01@users.noreply.github.com> Date: Fri, 17 Jan 2025 02:26:06 -0600 Subject: [PATCH] feat(client): persist playback rate in lecture videos (#58087) --- client/package.json | 6 +- .../Challenges/components/video-player.tsx | 10 +- .../src/templates/Challenges/generic/show.tsx | 10 +- client/src/templates/Challenges/video.css | 3 +- e2e/video-player.spec.ts | 7 +- pnpm-lock.yaml | 137 +++++++++++------- 6 files changed, 108 insertions(+), 65 deletions(-) diff --git a/client/package.json b/client/package.json index 7420c9cb6c2..a6f0c087266 100644 --- a/client/package.json +++ b/client/package.json @@ -81,12 +81,12 @@ "gatsby-source-filesystem": "3.15.0", "gatsby-transformer-remark": "5.25.1", "i18next": "22.5.1", + "instantsearch.js": "4.75.3", "lodash": "4.17.21", "lodash-es": "4.17.21", "micromark": "4.0.0", "monaco-editor": "0.28.1", "nanoid": "3.3.7", - "instantsearch.js": "4.75.3", "normalize-url": "4.5.1", "path-browserify": "1.0.1", "postcss": "8.4.35", @@ -102,8 +102,8 @@ "react-helmet": "6.1.0", "react-hotkeys": "2.0.0", "react-i18next": "12.3.1", - "react-instantsearch-core": "7.13.6", "react-instantsearch": "7.13.6", + "react-instantsearch-core": "7.13.6", "react-monaco-editor": "0.40.0", "react-redux": "7.2.9", "react-reflex": "4.1.0", @@ -112,7 +112,7 @@ "react-spinkit": "3.0.0", "react-tooltip": "4.5.1", "react-transition-group": "4.4.5", - "react-youtube": "7.14.0", + "react-youtube": "10.1.0", "redux": "4.2.1", "redux-actions": "2.6.5", "redux-observable": "1.2.0", diff --git a/client/src/templates/Challenges/components/video-player.tsx b/client/src/templates/Challenges/components/video-player.tsx index 1f068d686f2..8d34a8b62f7 100644 --- a/client/src/templates/Challenges/components/video-player.tsx +++ b/client/src/templates/Challenges/components/video-player.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import YouTube from 'react-youtube'; +import YouTube, { YouTubeEvent } from 'react-youtube'; +import store from 'store'; import Loader from '../../../components/helpers/loader'; import envData from '../../../../config/env.json'; @@ -19,12 +20,16 @@ const { clientLocale } = envData as { interface VideoPlayerProps { videoId: string; videoLocaleIds?: VideoLocaleIds; - onVideoLoad: () => void; + onVideoLoad: (e: YouTubeEvent) => void; videoIsLoaded: boolean; bilibiliIds?: BilibiliIds; title: string; } +function setPlaybackRate(e: YouTubeEvent) { + store.set('fcc-yt-playback-rate', e.data); +} + function VideoPlayer({ videoId, videoLocaleIds, @@ -69,6 +74,7 @@ function VideoPlayer({ videoIsLoaded ? 'display-youtube-video' : 'hide-youtube-video' } onReady={onVideoLoad} + onPlaybackRateChange={setPlaybackRate} opts={{ playerVars: { rel: 0 diff --git a/client/src/templates/Challenges/generic/show.tsx b/client/src/templates/Challenges/generic/show.tsx index ca502f4623c..4844484f2df 100644 --- a/client/src/templates/Challenges/generic/show.tsx +++ b/client/src/templates/Challenges/generic/show.tsx @@ -5,6 +5,8 @@ import { useTranslation } from 'react-i18next'; import { connect } from 'react-redux'; import { Container, Col, Row, Button, Spacer } from '@freecodecamp/ui'; import { isEqual } from 'lodash'; +import store from 'store'; +import { YouTubeEvent } from 'react-youtube'; // Local Utilities import LearnLayout from '../../../components/layouts/learn'; @@ -127,7 +129,13 @@ const ShowGeneric = ({ // video const [videoIsLoaded, setVideoIsLoaded] = useState(false); - const handleVideoIsLoaded = () => { + const handleVideoIsLoaded = (e: YouTubeEvent) => { + const playbackRate = Number(store.get('fcc-yt-playback-rate')) || 1; + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const player = e.target; + // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access + player.setPlaybackRate(playbackRate); + setVideoIsLoaded(true); }; diff --git a/client/src/templates/Challenges/video.css b/client/src/templates/Challenges/video.css index c1b8e598957..7a2afd6f98e 100644 --- a/client/src/templates/Challenges/video.css +++ b/client/src/templates/Challenges/video.css @@ -7,7 +7,8 @@ } .video-wrapper iframe, .video-wrapper object, -.video-wrapper embed { +.video-wrapper embed, +.video-wrapper .display-youtube-video { position: absolute; top: 0; left: 0; diff --git a/e2e/video-player.spec.ts b/e2e/video-player.spec.ts index e59d751e6ec..6efd466e83d 100644 --- a/e2e/video-player.spec.ts +++ b/e2e/video-player.spec.ts @@ -8,13 +8,10 @@ test.beforeEach(async ({ page }) => { test.describe('Challenge Video Player Component Tests', () => { test('should render video player and play button', async ({ page }) => { + await expect(page.locator('.display-youtube-video')).toBeVisible(); + await expect( page.locator('iframe[title="YouTube video player"]') ).toBeVisible(); - await expect( - page - .frameLocator('.display-youtube-video') - .getByRole('button', { name: 'Play' }) - ).toBeVisible(); }); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4423967ba28..423f0cff530 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -657,8 +657,8 @@ importers: specifier: 4.4.5 version: 4.4.5(react-dom@16.14.0(react@16.14.0))(react@16.14.0) react-youtube: - specifier: 7.14.0 - version: 7.14.0(react@16.14.0) + specifier: 10.1.0 + version: 10.1.0(react@16.14.0) redux: specifier: 4.2.1 version: 4.2.1 @@ -833,7 +833,7 @@ importers: version: 10.9.2(@types/node@20.12.8)(typescript@5.2.2) webpack: specifier: 5.90.3 - version: 5.90.3(webpack-cli@4.10.0) + version: 5.90.3 curriculum: devDependencies: @@ -1122,7 +1122,7 @@ importers: version: 4.3.12 '@types/copy-webpack-plugin': specifier: ^8.0.1 - version: 8.0.1(webpack-cli@4.10.0) + version: 8.0.1(webpack-cli@4.10.0(webpack-bundle-analyzer@4.10.1)(webpack@5.90.3)) '@types/enzyme': specifier: 3.10.16 version: 3.10.16 @@ -1140,13 +1140,13 @@ importers: version: 1.6.0(typescript@5.4.5) babel-loader: specifier: 8.3.0 - version: 8.3.0(@babel/core@7.23.7)(webpack@5.90.3) + version: 8.3.0(@babel/core@7.23.7)(webpack@5.90.3(webpack-cli@4.10.0)) chai: specifier: 4.4.1 version: 4.4.1 copy-webpack-plugin: specifier: 9.1.0 - version: 9.1.0(webpack@5.90.3) + version: 9.1.0(webpack@5.90.3(webpack-cli@4.10.0)) enzyme: specifier: 3.11.0 version: 3.11.0 @@ -11126,9 +11126,6 @@ packages: prop-types-exact@1.2.0: resolution: {integrity: sha512-K+Tk3Kd9V0odiXFP9fwDHUYRyvK3Nun3GVyPapSIs5OBkITAm15W0CPFD/YKTkMUAbc0b9CUwRQp2ybiBIq+eA==} - prop-types@15.7.2: - resolution: {integrity: sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==} - prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -11481,9 +11478,9 @@ packages: react: '>=16.6.0' react-dom: '>=16.6.0' - react-youtube@7.14.0: - resolution: {integrity: sha512-SUHZ4F4pd1EHmQu0CV0KSQvAs5KHOT5cfYaq4WLCcDbU8fBo1ouTXaAOIASWbrz8fHwg+G1evfoSIYpV2AwSAg==} - engines: {node: '>= 10.x'} + react-youtube@10.1.0: + resolution: {integrity: sha512-ZfGtcVpk0SSZtWCSTYOQKhfx5/1cfyEW1JN/mugGNfAxT3rmVJeMbGpA9+e78yG21ls5nc/5uZJETE3cm3knBg==} + engines: {node: '>= 14.x'} peerDependencies: react: '>=0.14.1' @@ -17692,7 +17689,7 @@ snapshots: react-refresh: 0.9.0 schema-utils: 2.7.1 source-map: 0.7.4 - webpack: 5.90.3(webpack-cli@4.10.0) + webpack: 5.90.3 '@polka/url@1.0.0-next.23': {} @@ -18432,7 +18429,7 @@ snapshots: '@types/cookiejar@2.1.2': {} - '@types/copy-webpack-plugin@8.0.1(webpack-cli@4.10.0)': + '@types/copy-webpack-plugin@8.0.1(webpack-cli@4.10.0(webpack-bundle-analyzer@4.10.1)(webpack@5.90.3))': dependencies: '@types/node': 20.8.0 tapable: 2.2.1 @@ -19181,17 +19178,17 @@ snapshots: '@webassemblyjs/ast': 1.11.6 '@xtuc/long': 4.2.2 - '@webpack-cli/configtest@1.2.0(webpack-cli@4.10.0)(webpack@5.90.3)': + '@webpack-cli/configtest@1.2.0(webpack-cli@4.10.0(webpack-bundle-analyzer@4.10.1)(webpack@5.90.3))(webpack@5.90.3(webpack-cli@4.10.0))': dependencies: webpack: 5.90.3(webpack-cli@4.10.0) webpack-cli: 4.10.0(webpack-bundle-analyzer@4.10.1)(webpack@5.90.3) - '@webpack-cli/info@1.5.0(webpack-cli@4.10.0)': + '@webpack-cli/info@1.5.0(webpack-cli@4.10.0(webpack-bundle-analyzer@4.10.1)(webpack@5.90.3))': dependencies: envinfo: 7.10.0 webpack-cli: 4.10.0(webpack-bundle-analyzer@4.10.1)(webpack@5.90.3) - '@webpack-cli/serve@1.7.0(webpack-cli@4.10.0)': + '@webpack-cli/serve@1.7.0(webpack-cli@4.10.0(webpack-bundle-analyzer@4.10.1)(webpack@5.90.3))': dependencies: webpack-cli: 4.10.0(webpack-bundle-analyzer@4.10.1)(webpack@5.90.3) @@ -19675,9 +19672,9 @@ snapshots: loader-utils: 2.0.4 make-dir: 3.1.0 schema-utils: 2.7.1 - webpack: 5.90.3(webpack-cli@4.10.0) + webpack: 5.90.3 - babel-loader@8.3.0(@babel/core@7.23.7)(webpack@5.90.3): + babel-loader@8.3.0(@babel/core@7.23.7)(webpack@5.90.3(webpack-cli@4.10.0)): dependencies: '@babel/core': 7.23.7 find-cache-dir: 3.3.2 @@ -20804,7 +20801,7 @@ snapshots: copy-descriptor@0.1.1: {} - copy-webpack-plugin@9.1.0(webpack@5.90.3): + copy-webpack-plugin@9.1.0(webpack@5.90.3(webpack-cli@4.10.0)): dependencies: fast-glob: 3.3.1 glob-parent: 6.0.2 @@ -20996,7 +20993,7 @@ snapshots: postcss-value-parser: 4.2.0 schema-utils: 3.3.0 semver: 7.6.0 - webpack: 5.90.3(webpack-cli@4.10.0) + webpack: 5.90.3 css-mediaquery@0.1.2: {} @@ -21009,7 +21006,7 @@ snapshots: schema-utils: 3.3.0 serialize-javascript: 5.0.1 source-map: 0.6.1 - webpack: 5.90.3(webpack-cli@4.10.0) + webpack: 5.90.3 css-select@4.3.0: dependencies: @@ -21944,7 +21941,7 @@ snapshots: debug: 4.3.4(supports-color@8.1.1) enhanced-resolve: 5.15.0 eslint: 8.57.0 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@7.1.1(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.5.5)(eslint@8.57.0) + eslint-module-utils: 2.8.0(@typescript-eslint/parser@7.1.1(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.5.5(@typescript-eslint/parser@7.1.1(eslint@8.57.0)(typescript@5.4.5))(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.5.5)(eslint@8.57.0) get-tsconfig: 4.7.2 globby: 13.2.2 @@ -21968,7 +21965,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.8.0(@typescript-eslint/parser@7.1.1(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.5.5)(eslint@8.57.0): + eslint-module-utils@2.8.0(@typescript-eslint/parser@7.1.1(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.5.5(@typescript-eslint/parser@7.1.1(eslint@8.57.0)(typescript@5.4.5))(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0): dependencies: debug: 3.2.7(supports-color@5.5.0) optionalDependencies: @@ -22041,7 +22038,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@7.1.1(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.5.5)(eslint@8.57.0) + eslint-module-utils: 2.8.0(@typescript-eslint/parser@7.1.1(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.5.5(@typescript-eslint/parser@7.1.1(eslint@8.57.0)(typescript@5.4.5))(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) hasown: 2.0.0 is-core-module: 2.13.1 is-glob: 4.0.3 @@ -22222,7 +22219,7 @@ snapshots: micromatch: 4.0.8 normalize-path: 3.0.0 schema-utils: 3.3.0 - webpack: 5.90.3(webpack-cli@4.10.0) + webpack: 5.90.3 eslint@7.32.0: dependencies: @@ -22713,7 +22710,7 @@ snapshots: dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.90.3(webpack-cli@4.10.0) + webpack: 5.90.3 file-type@16.5.4: dependencies: @@ -22844,7 +22841,7 @@ snapshots: semver: 5.7.2 tapable: 1.1.3 typescript: 5.2.2 - webpack: 5.90.3(webpack-cli@4.10.0) + webpack: 5.90.3 worker-rpc: 0.1.1 optionalDependencies: eslint: 7.32.0 @@ -23419,7 +23416,7 @@ snapshots: url-loader: 4.1.1(file-loader@6.2.0(webpack@5.90.3))(webpack@5.90.3) uuid: 3.4.0 v8-compile-cache: 2.4.0 - webpack: 5.90.3(webpack-cli@4.10.0) + webpack: 5.90.3 webpack-dev-middleware: 4.3.0(webpack@5.90.3) webpack-merge: 5.9.0 webpack-stats-plugin: 1.1.3 @@ -26531,7 +26528,7 @@ snapshots: dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.90.3(webpack-cli@4.10.0) + webpack: 5.90.3 webpack-sources: 1.4.3 minimalistic-assert@1.0.1: {} @@ -26635,7 +26632,7 @@ snapshots: dependencies: loader-utils: 2.0.4 monaco-editor: 0.28.1 - webpack: 5.90.3(webpack-cli@4.10.0) + webpack: 5.90.3 monaco-editor@0.28.1: {} @@ -26937,7 +26934,7 @@ snapshots: dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.90.3(webpack-cli@4.10.0) + webpack: 5.90.3 nwsapi@2.2.7: {} @@ -27518,7 +27515,7 @@ snapshots: postcss: 8.4.35 schema-utils: 3.3.0 semver: 7.6.0 - webpack: 5.90.3(webpack-cli@4.10.0) + webpack: 5.90.3 postcss-loader@5.3.0(postcss@8.4.35)(webpack@5.90.3): dependencies: @@ -27526,7 +27523,7 @@ snapshots: klona: 2.0.6 postcss: 8.4.35 semver: 7.6.0 - webpack: 5.90.3(webpack-cli@4.10.0) + webpack: 5.90.3 postcss-merge-longhand@5.1.7(postcss@8.4.35): dependencies: @@ -27786,12 +27783,6 @@ snapshots: object.assign: 4.1.5 reflect.ownkeys: 0.2.0 - prop-types@15.7.2: - dependencies: - loose-envify: 1.4.0 - object-assign: 4.1.1 - react-is: 16.13.1 - prop-types@15.8.1: dependencies: loose-envify: 1.4.0 @@ -28007,7 +27998,7 @@ snapshots: dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.90.3(webpack-cli@4.10.0) + webpack: 5.90.3 rc@1.2.8: dependencies: @@ -28042,7 +28033,7 @@ snapshots: shell-quote: 1.7.2 strip-ansi: 6.0.0 text-table: 0.2.0 - webpack: 5.90.3(webpack-cli@4.10.0) + webpack: 5.90.3 optionalDependencies: typescript: 5.2.2 transitivePeerDependencies: @@ -28223,10 +28214,10 @@ snapshots: react: 16.14.0 react-dom: 16.14.0(react@16.14.0) - react-youtube@7.14.0(react@16.14.0): + react-youtube@10.1.0(react@16.14.0): dependencies: fast-deep-equal: 3.1.3 - prop-types: 15.7.2 + prop-types: 15.8.1 react: 16.14.0 youtube-player: 5.5.2 transitivePeerDependencies: @@ -29552,7 +29543,7 @@ snapshots: dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.90.3(webpack-cli@4.10.0) + webpack: 5.90.3 style-to-object@0.3.0: dependencies: @@ -29752,7 +29743,7 @@ snapshots: term-size@2.2.1: {} - terser-webpack-plugin@5.3.10(webpack@5.90.3): + terser-webpack-plugin@5.3.10(webpack@5.90.3(webpack-cli@4.10.0)): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 @@ -29761,6 +29752,15 @@ snapshots: terser: 5.28.1 webpack: 5.90.3(webpack-cli@4.10.0) + terser-webpack-plugin@5.3.10(webpack@5.90.3): + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + jest-worker: 27.5.1 + schema-utils: 3.3.0 + serialize-javascript: 6.0.1 + terser: 5.28.1 + webpack: 5.90.3 + terser-webpack-plugin@5.3.9(webpack@5.90.3): dependencies: '@jridgewell/trace-mapping': 0.3.22 @@ -29768,7 +29768,7 @@ snapshots: schema-utils: 3.3.0 serialize-javascript: 6.0.1 terser: 5.20.0 - webpack: 5.90.3(webpack-cli@4.10.0) + webpack: 5.90.3 terser@5.20.0: dependencies: @@ -30418,7 +30418,7 @@ snapshots: loader-utils: 2.0.4 mime-types: 2.1.35 schema-utils: 3.3.0 - webpack: 5.90.3(webpack-cli@4.10.0) + webpack: 5.90.3 optionalDependencies: file-loader: 6.2.0(webpack@5.90.3) @@ -30633,9 +30633,9 @@ snapshots: webpack-cli@4.10.0(webpack-bundle-analyzer@4.10.1)(webpack@5.90.3): dependencies: '@discoveryjs/json-ext': 0.5.7 - '@webpack-cli/configtest': 1.2.0(webpack-cli@4.10.0)(webpack@5.90.3) - '@webpack-cli/info': 1.5.0(webpack-cli@4.10.0) - '@webpack-cli/serve': 1.7.0(webpack-cli@4.10.0) + '@webpack-cli/configtest': 1.2.0(webpack-cli@4.10.0(webpack-bundle-analyzer@4.10.1)(webpack@5.90.3))(webpack@5.90.3(webpack-cli@4.10.0)) + '@webpack-cli/info': 1.5.0(webpack-cli@4.10.0(webpack-bundle-analyzer@4.10.1)(webpack@5.90.3)) + '@webpack-cli/serve': 1.7.0(webpack-cli@4.10.0(webpack-bundle-analyzer@4.10.1)(webpack@5.90.3)) colorette: 2.0.20 commander: 7.2.0 cross-spawn: 7.0.3 @@ -30656,7 +30656,7 @@ snapshots: mime-types: 2.1.35 range-parser: 1.2.1 schema-utils: 3.3.0 - webpack: 5.90.3(webpack-cli@4.10.0) + webpack: 5.90.3 webpack-merge@5.9.0: dependencies: @@ -30678,6 +30678,37 @@ snapshots: transitivePeerDependencies: - supports-color + webpack@5.90.3: + dependencies: + '@types/eslint-scope': 3.7.5 + '@types/estree': 1.0.5 + '@webassemblyjs/ast': 1.11.6 + '@webassemblyjs/wasm-edit': 1.11.6 + '@webassemblyjs/wasm-parser': 1.11.6 + acorn: 8.11.3 + acorn-import-assertions: 1.9.0(acorn@8.11.3) + browserslist: 4.23.0 + chrome-trace-event: 1.0.3 + enhanced-resolve: 5.15.0 + es-module-lexer: 1.3.1 + eslint-scope: 5.1.1 + events: 3.3.0 + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + json-parse-even-better-errors: 2.3.1 + loader-runner: 4.3.0 + mime-types: 2.1.35 + neo-async: 2.6.2 + schema-utils: 3.3.0 + tapable: 2.2.1 + terser-webpack-plugin: 5.3.10(webpack@5.90.3) + watchpack: 2.4.0 + webpack-sources: 3.2.3 + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + webpack@5.90.3(webpack-cli@4.10.0): dependencies: '@types/eslint-scope': 3.7.5 @@ -30701,7 +30732,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.10(webpack@5.90.3) + terser-webpack-plugin: 5.3.10(webpack@5.90.3(webpack-cli@4.10.0)) watchpack: 2.4.0 webpack-sources: 3.2.3 optionalDependencies: