diff --git a/src/components/course/course-header/tests/CoursePreview.test.jsx b/src/components/course/course-header/tests/CoursePreview.test.jsx
index cf11e23688..9c142f9571 100644
--- a/src/components/course/course-header/tests/CoursePreview.test.jsx
+++ b/src/components/course/course-header/tests/CoursePreview.test.jsx
@@ -11,6 +11,11 @@ const imageURL = 'https://test-domain.com/test-image/id.png';
const hlsUrl = 'https://test-domain.com/test-prefix/id.m3u8';
const ytUrl = 'https://www.youtube.com/watch?v=oHg5SJYRHA0';
+jest.mock('@edx/frontend-platform/i18n', () => ({
+ ...jest.requireActual('@edx/frontend-platform/i18n'),
+ getLocale: () => 'en',
+}));
+
describe('Course Preview Tests', () => {
it('Renders preview image and not the video when video URL is not given.', () => {
const { container, getByAltText } = renderWithRouter();
diff --git a/src/components/microlearning/styles/VideoDetailPage.scss b/src/components/microlearning/styles/VideoDetailPage.scss
index 835aeadf6e..08c18d917f 100644
--- a/src/components/microlearning/styles/VideoDetailPage.scss
+++ b/src/components/microlearning/styles/VideoDetailPage.scss
@@ -29,6 +29,7 @@
.video-player-container-with-transcript {
display: flex;
+ padding-bottom: 55px;
}
.video-js-wrapper {
diff --git a/src/components/video/VideoJS.jsx b/src/components/video/VideoJS.jsx
index 3cb25a6e81..03eab95476 100644
--- a/src/components/video/VideoJS.jsx
+++ b/src/components/video/VideoJS.jsx
@@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
import 'videojs-youtube';
import videojs from 'video.js';
import 'video.js/dist/video-js.css';
+import { getLocale } from '@edx/frontend-platform/i18n';
import { PLAYBACK_RATES } from './data/constants';
import { usePlayerOptions, useTranscripts } from './data';
@@ -14,10 +15,12 @@ require('videojs-vjstranscribe');
const VideoJS = ({ options, onReady, customOptions }) => {
const videoRef = useRef(null);
const playerRef = useRef(null);
+ const siteLanguage = getLocale();
const transcripts = useTranscripts({
player: playerRef.current,
customOptions,
+ siteLanguage,
});
const playerOptions = usePlayerOptions({
diff --git a/src/components/video/data/hooks.js b/src/components/video/data/hooks.js
index cf5100a165..a54bcdf643 100644
--- a/src/components/video/data/hooks.js
+++ b/src/components/video/data/hooks.js
@@ -2,11 +2,12 @@ import { useEffect, useState } from 'react';
import { logError } from '@edx/frontend-platform/logging';
import { fetchAndAddTranscripts } from './service';
+import { sortTextTracks } from './utils';
-export function useTranscripts({ player, customOptions }) {
+export function useTranscripts({ player, customOptions, siteLanguage }) {
const shouldUseTranscripts = !!(customOptions?.showTranscripts && customOptions?.transcriptUrls);
const [isLoading, setIsLoading] = useState(shouldUseTranscripts);
- const [textTracks, setTextTracks] = useState([]);
+ const [textTracks, setTextTracks] = useState({});
const [transcriptUrl, setTranscriptUrl] = useState(null);
useEffect(() => {
@@ -15,12 +16,15 @@ export function useTranscripts({ player, customOptions }) {
if (shouldUseTranscripts) {
try {
const result = await fetchAndAddTranscripts(customOptions.transcriptUrls, player);
- setTextTracks(result);
- // We are only catering to English transcripts for now as we don't have the option to change
- // the transcript language yet.
- if (result.en) {
- setTranscriptUrl(result.en);
- }
+
+ // Sort the text tracks to prioritize the site language at the top of the list.
+ // Currently, video.js selects the top language from the list of transcripts.
+ const sortedResult = sortTextTracks(result, siteLanguage);
+ setTextTracks(sortedResult);
+
+ // Default to site language, fallback to English
+ const preferredTranscript = sortedResult[siteLanguage] || sortedResult.en;
+ setTranscriptUrl(preferredTranscript);
} catch (error) {
logError(`Error fetching transcripts for player: ${error}`);
} finally {
@@ -29,7 +33,7 @@ export function useTranscripts({ player, customOptions }) {
}
};
fetchFn();
- }, [customOptions?.transcriptUrls, player, shouldUseTranscripts]);
+ }, [customOptions?.transcriptUrls, player, shouldUseTranscripts, siteLanguage]);
return {
textTracks,
diff --git a/src/components/video/data/tests/hooks.test.js b/src/components/video/data/tests/hooks.test.js
index 1c0b24a2fb..da92e51a6c 100644
--- a/src/components/video/data/tests/hooks.test.js
+++ b/src/components/video/data/tests/hooks.test.js
@@ -49,7 +49,7 @@ describe('useTranscripts', () => {
expect(logError).toHaveBeenCalledWith(`Error fetching transcripts for player: Error: ${errorMessage}`);
expect(result.current.isLoading).toBe(false);
- expect(result.current.textTracks).toEqual([]);
+ expect(result.current.textTracks).toEqual({});
expect(result.current.transcriptUrl).toBeNull();
});
@@ -64,7 +64,7 @@ describe('useTranscripts', () => {
customOptions: customOptionsWithoutTranscripts,
}));
- expect(result.current.textTracks).toEqual([]);
+ expect(result.current.textTracks).toEqual({});
expect(result.current.transcriptUrl).toBeNull();
});
});
diff --git a/src/components/video/data/tests/utils.test.js b/src/components/video/data/tests/utils.test.js
index a131128901..242ddb3f7c 100644
--- a/src/components/video/data/tests/utils.test.js
+++ b/src/components/video/data/tests/utils.test.js
@@ -1,4 +1,4 @@
-import { convertToWebVtt, createWebVttFile } from '../utils';
+import { convertToWebVtt, createWebVttFile, sortTextTracks } from '../utils';
describe('Video utils tests', () => {
it('should convert transcript data to WebVTT format correctly', () => {
@@ -40,4 +40,22 @@ describe('Video utils tests', () => {
expect(blob.type).toBe('text/vtt');
expect(blob.size).toBe(mockWebVttContent.length);
});
+ it('should sort text tracks with site language first and others alphabetically', () => {
+ const mockTracks = {
+ en: 'https://test-domain.com/transcript-en.txt',
+ ar: 'https://test-domain.com/transcript-ar.txt',
+ fr: 'https://test-domain.com/transcript-fr.txt',
+ };
+
+ const siteLanguage = 'fr';
+
+ const expectedSortedTracks = {
+ fr: 'https://test-domain.com/transcript-fr.txt',
+ ar: 'https://test-domain.com/transcript-ar.txt',
+ en: 'https://test-domain.com/transcript-en.txt',
+ };
+
+ const result = sortTextTracks(mockTracks, siteLanguage);
+ expect(result).toEqual(expectedSortedTracks);
+ });
});
diff --git a/src/components/video/data/utils.js b/src/components/video/data/utils.js
index d75d693e39..939348687c 100644
--- a/src/components/video/data/utils.js
+++ b/src/components/video/data/utils.js
@@ -26,3 +26,16 @@ export const createWebVttFile = (webVttContent) => {
const blob = new Blob([webVttContent], { type: 'text/vtt' });
return URL.createObjectURL(blob);
};
+
+export const sortTextTracks = (tracks, siteLanguage) => {
+ const sortedKeys = Object.keys(tracks).sort((a, b) => {
+ if (a === siteLanguage) { return -1; }
+ if (b === siteLanguage) { return 1; }
+ return a.localeCompare(b);
+ });
+
+ return sortedKeys.reduce((acc, key) => {
+ acc[key] = tracks[key];
+ return acc;
+ }, {});
+};
diff --git a/src/components/video/tests/VideoJS.test.jsx b/src/components/video/tests/VideoJS.test.jsx
index c21aaf0be0..e559c25360 100644
--- a/src/components/video/tests/VideoJS.test.jsx
+++ b/src/components/video/tests/VideoJS.test.jsx
@@ -11,6 +11,11 @@ jest.mock('../data', () => ({
usePlayerOptions: jest.fn(),
}));
+jest.mock('@edx/frontend-platform/i18n', () => ({
+ ...jest.requireActual('@edx/frontend-platform/i18n'),
+ getLocale: () => 'en',
+}));
+
const hlsUrl = 'https://test-domain.com/test-prefix/id.m3u8';
const ytUrl = 'https://www.youtube.com/watch?v=oHg5SJYRHA0';
diff --git a/src/components/video/tests/VideoPlayer.test.jsx b/src/components/video/tests/VideoPlayer.test.jsx
index 61711c6108..8b1b991353 100644
--- a/src/components/video/tests/VideoPlayer.test.jsx
+++ b/src/components/video/tests/VideoPlayer.test.jsx
@@ -7,6 +7,11 @@ const hlsUrl = 'https://test-domain.com/test-prefix/id.m3u8';
const ytUrl = 'https://www.youtube.com/watch?v=oHg5SJYRHA0';
const mp3Url = 'https://example.com/audio.mp3';
+jest.mock('@edx/frontend-platform/i18n', () => ({
+ ...jest.requireActual('@edx/frontend-platform/i18n'),
+ getLocale: () => 'en',
+}));
+
describe('Video Player component', () => {
it('Renders Video Player components correctly for HLS videos.', async () => {
const { container } = renderWithRouter();