Skip to content

Commit f849667

Browse files
committed
feat: add generated video transcriptions to dash preview
feat: Refactor dashviewer transcription handling to await
1 parent 5f3aae7 commit f849667

6 files changed

Lines changed: 378 additions & 5 deletions

File tree

src/lib/__tests__/Preview-test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2724,7 +2724,7 @@ describe('lib/Preview', () => {
27242724

27252725
test('should add dash hints if the browser supports dash', () => {
27262726
stubs.canPlayDash.mockReturnValue(true);
2727-
stubs.headers['X-Rep-Hints'] += '[dash,mp4][filmstrip]';
2727+
stubs.headers['X-Rep-Hints'] += '[dash,mp4][filmstrip][extracted_text]';
27282728

27292729
preview.getRequestHeaders();
27302730
expect(stubs.getHeaders).toHaveBeenCalledWith(stubs.headers, 'previewtoken', 'link', 'Passw0rd!');

src/lib/constants.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ export const STATUS_VIEWABLE = 'viewable';
9797
export const X_REP_HINT_BASE = '[3d][pdf][text][mp3][json]';
9898
export const X_REP_HINT_DOC_THUMBNAIL = '[jpg?dimensions=1024x1024&paged=false]';
9999
export const X_REP_HINT_IMAGE = '[jpg?dimensions=2048x2048,png?dimensions=2048x2048]';
100-
export const X_REP_HINT_VIDEO_DASH = '[dash,mp4][filmstrip]';
100+
export const X_REP_HINT_VIDEO_DASH = '[dash,mp4][filmstrip][extracted_text]';
101101
export const X_REP_HINT_VIDEO_MP4 = '[mp4]';
102102

103103
export const PDFJS_CSS_UNITS = 96.0 / 72.0; // Should match CSS_UNITS in pdf_viewer.js

src/lib/viewers/media/DashViewer.js

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ class DashViewer extends VideoBaseViewer {
4646
/** @property {Object} - Status of the filmstrip representation */
4747
filmstripStatus;
4848

49+
/** @property {Object} - Status of the extracted_text (transcription) representation */
50+
transcriptionStatus;
51+
4952
/** @property {string} - URL for the filmstrip image */
5053
filmstripUrl;
5154

@@ -164,6 +167,10 @@ class DashViewer extends VideoBaseViewer {
164167
this.filmstripStatus.destroy();
165168
}
166169

170+
if (this.transcriptionStatus) {
171+
this.transcriptionStatus.destroy();
172+
}
173+
167174
clearInterval(this.statsIntervalId);
168175
if (this.player) {
169176
this.player.destroy();
@@ -687,6 +694,17 @@ class DashViewer extends VideoBaseViewer {
687694
}
688695
}
689696

697+
/**
698+
* Returns the display-friendly name for a text track's language.
699+
* Maps the undetermined language code 'und' to a localized "Auto-Generated" label.
700+
*
701+
* @param {Object} track - A Shaka text track object
702+
* @return {string} Localized language name or the raw language code
703+
*/
704+
getTrackDisplayLanguage(track) {
705+
return track.language === 'und' ? __('auto_generated') : getLanguageName(track.language) || track.language;
706+
}
707+
690708
/**
691709
* Loads captions/subtitles into the settings menu
692710
*
@@ -701,7 +719,7 @@ class DashViewer extends VideoBaseViewer {
701719
this.initSubtitles();
702720
} else {
703721
this.mediaControls.initSubtitles(
704-
this.textTracks.map(track => getLanguageName(track.language) || track.language),
722+
this.textTracks.map(track => this.getTrackDisplayLanguage(track)),
705723
getLanguageName(this.options.location.locale.substring(0, 2)),
706724
);
707725
}
@@ -741,7 +759,7 @@ class DashViewer extends VideoBaseViewer {
741759

742760
this.textTracks = this.textTracks.map(track => ({
743761
...track,
744-
displayLanguage: getLanguageName(track.language) || track.language,
762+
displayLanguage: this.getTrackDisplayLanguage(track),
745763
}));
746764

747765
// Do intelligent selection: Prefer user's language, fallback to English, then first subtitle in list
@@ -895,6 +913,7 @@ class DashViewer extends VideoBaseViewer {
895913
this.startBandwidthTracking();
896914
this.loadFilmStrip();
897915
this.loadSubtitles();
916+
this.loadTranscription();
898917
this.loadAlternateAudio();
899918
this.showPlayButton();
900919

@@ -967,6 +986,69 @@ class DashViewer extends VideoBaseViewer {
967986
}
968987
}
969988

989+
/**
990+
* Loads the extracted_text transcription (.vtt) as a text track when available
991+
*
992+
* @private
993+
* @return {void}
994+
*/
995+
async loadTranscription() {
996+
const extractedText = getRepresentation(this.options.file, 'extracted_text');
997+
if (!extractedText?.content?.url_template) {
998+
return;
999+
}
1000+
1001+
const transcriptionUrl = this.createContentUrlWithAuthParams(extractedText.content.url_template);
1002+
this.transcriptionStatus = this.getRepStatus(extractedText);
1003+
1004+
try {
1005+
await this.transcriptionStatus.getPromise();
1006+
1007+
if (this.isDestroyed() || !this.player) {
1008+
return;
1009+
}
1010+
1011+
await this.player.addTextTrackAsync(
1012+
transcriptionUrl,
1013+
'und',
1014+
'subtitles',
1015+
'text/vtt',
1016+
undefined,
1017+
__('auto_generated'),
1018+
);
1019+
1020+
if (this.isDestroyed()) {
1021+
return;
1022+
}
1023+
1024+
if (this.textTracks.length > 0) {
1025+
// Subtitles were already initialized — find and append only
1026+
// the new track(s) without disturbing the user's selection.
1027+
const prevTracks = this.textTracks;
1028+
const prevIds = new Set(prevTracks.map(t => t.id));
1029+
this.textTracks = this.player.getTextTracks().sort((track1, track2) => track1.id - track2.id);
1030+
1031+
if (this.useReactControls()) {
1032+
this.textTracks = this.textTracks.map(track => ({
1033+
...track,
1034+
displayLanguage: this.getTrackDisplayLanguage(track),
1035+
}));
1036+
this.renderUI();
1037+
} else {
1038+
this.textTracks.forEach((track, idx) => {
1039+
if (!prevIds.has(track.id)) {
1040+
this.mediaControls.settings.addSubtitle(__('auto_generated'), idx);
1041+
}
1042+
});
1043+
}
1044+
} else {
1045+
this.loadSubtitles();
1046+
}
1047+
} catch {
1048+
// Transcription is non-critical; allow the viewer to continue without it
1049+
}
1050+
}
1051+
9701052
/**
9711053
* Calculates the video dimension based on representations
9721054
*

src/lib/viewers/media/Settings.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,27 @@ class Settings extends EventEmitter {
692692
this.reset();
693693
}
694694

695+
/**
696+
* Appends a single subtitle entry to the settings menu without disturbing
697+
* existing items or the user's current selection.
698+
*
699+
* @param {string} subtitle - Display name for the subtitle track
700+
* @param {number} idx - data-value index for the new menu item
701+
* @return {void}
702+
*/
703+
addSubtitle(subtitle, idx) {
704+
const subtitlesSubMenu = this.settingsEl.querySelector('.bp-media-settings-menu-subtitles');
705+
insertTemplate(
706+
subtitlesSubMenu,
707+
SUBMENU_SUBITEM_TEMPLATE.replace(/{{dataType}}/g, 'subtitles').replace(/{{dataValue}}/g, idx),
708+
);
709+
const languageNode = subtitlesSubMenu.lastChild.querySelector('.bp-media-settings-value');
710+
languageNode.textContent = subtitle;
711+
712+
this.subtitles.push(subtitle);
713+
this.reset();
714+
}
715+
695716
/**
696717
* Takes an ordered list of audio languages and populates the settings menu
697718
*

0 commit comments

Comments
 (0)