Skip to content

Commit 7f16525

Browse files
committed
feat: add generated video transcriptions to dash preview
feat: Refactor dashviewer transcription handling to await
1 parent 290dece commit 7f16525

6 files changed

Lines changed: 379 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
@@ -2924,7 +2924,7 @@ describe('lib/Preview', () => {
29242924

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

29292929
preview.getRequestHeaders();
29302930
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
@@ -100,7 +100,7 @@ export const STATUS_VIEWABLE = 'viewable';
100100
export const X_REP_HINT_BASE = '[3d][pdf][text][mp3][json]';
101101
export const X_REP_HINT_DOC_THUMBNAIL = '[jpg?dimensions=1024x1024&paged=false]';
102102
export const X_REP_HINT_IMAGE = '[jpg?dimensions=2048x2048,png?dimensions=2048x2048]';
103-
export const X_REP_HINT_VIDEO_DASH = '[dash,mp4][filmstrip]';
103+
export const X_REP_HINT_VIDEO_DASH = '[dash,mp4][filmstrip][extracted_text]';
104104
export const X_REP_HINT_VIDEO_MP4 = '[mp4]';
105105

106106
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
@@ -33,6 +33,9 @@ class DashViewer extends VideoBaseViewer {
3333
/** @property {Object} - Status of the filmstrip representation */
3434
filmstripStatus;
3535

36+
/** @property {Object} - Status of the extracted_text (transcription) representation */
37+
transcriptionStatus;
38+
3639
/** @property {string} - URL for the filmstrip image */
3740
filmstripUrl;
3841

@@ -117,6 +120,10 @@ class DashViewer extends VideoBaseViewer {
117120
this.filmstripStatus.destroy();
118121
}
119122

123+
if (this.transcriptionStatus) {
124+
this.transcriptionStatus.destroy();
125+
}
126+
120127
clearInterval(this.statsIntervalId);
121128
if (this.player) {
122129
this.player.destroy();
@@ -631,6 +638,17 @@ class DashViewer extends VideoBaseViewer {
631638
this.mediaControls.addListener('audiochange', this.handleAudioTrack);
632639
}
633640

641+
/**
642+
* Returns the display-friendly name for a text track's language.
643+
* Maps the undetermined language code 'und' to a localized "Auto-Generated" label.
644+
*
645+
* @param {Object} track - A Shaka text track object
646+
* @return {string} Localized language name or the raw language code
647+
*/
648+
getTrackDisplayLanguage(track) {
649+
return track.language === 'und' ? __('auto_generated') : getLanguageName(track.language) || track.language;
650+
}
651+
634652
/**
635653
* Loads captions/subtitles into the settings menu
636654
*
@@ -645,7 +663,7 @@ class DashViewer extends VideoBaseViewer {
645663
this.initSubtitles();
646664
} else {
647665
this.mediaControls.initSubtitles(
648-
this.textTracks.map(track => getLanguageName(track.language) || track.language),
666+
this.textTracks.map(track => this.getTrackDisplayLanguage(track)),
649667
getLanguageName(this.options.location.locale.substring(0, 2)),
650668
);
651669
}
@@ -685,7 +703,7 @@ class DashViewer extends VideoBaseViewer {
685703

686704
this.textTracks = this.textTracks.map(track => ({
687705
...track,
688-
displayLanguage: getLanguageName(track.language) || track.language,
706+
displayLanguage: this.getTrackDisplayLanguage(track),
689707
}));
690708

691709
// Do intelligent selection: Prefer user's language, fallback to English, then first subtitle in list
@@ -842,6 +860,7 @@ class DashViewer extends VideoBaseViewer {
842860
this.startBandwidthTracking();
843861
this.loadFilmStrip();
844862
this.loadSubtitles();
863+
this.loadTranscription();
845864
this.loadAlternateAudio();
846865
this.showPlayButton();
847866

@@ -921,6 +940,69 @@ class DashViewer extends VideoBaseViewer {
921940
}
922941
}
923942

943+
/**
944+
* Loads the extracted_text transcription (.vtt) as a text track when available
945+
*
946+
* @private
947+
* @return {void}
948+
*/
949+
async loadTranscription() {
950+
const extractedText = getRepresentation(this.options.file, 'extracted_text');
951+
if (!extractedText?.content?.url_template) {
952+
return;
953+
}
954+
955+
const transcriptionUrl = this.createContentUrlWithAuthParams(extractedText.content.url_template);
956+
this.transcriptionStatus = this.getRepStatus(extractedText);
957+
958+
try {
959+
await this.transcriptionStatus.getPromise();
960+
961+
if (this.isDestroyed() || !this.player) {
962+
return;
963+
}
964+
965+
await this.player.addTextTrackAsync(
966+
transcriptionUrl,
967+
'und',
968+
'subtitles',
969+
'text/vtt',
970+
undefined,
971+
__('auto_generated'),
972+
);
973+
974+
if (this.isDestroyed()) {
975+
return;
976+
}
977+
978+
if (this.textTracks.length > 0) {
979+
// Subtitles were already initialized — find and append only
980+
// the new track(s) without disturbing the user's selection.
981+
const prevTracks = this.textTracks;
982+
const prevIds = new Set(prevTracks.map(t => t.id));
983+
this.textTracks = this.player.getTextTracks().sort((track1, track2) => track1.id - track2.id);
984+
985+
if (this.useReactControls()) {
986+
this.textTracks = this.textTracks.map(track => ({
987+
...track,
988+
displayLanguage: this.getTrackDisplayLanguage(track),
989+
}));
990+
this.renderUI();
991+
} else {
992+
this.textTracks.forEach((track, idx) => {
993+
if (!prevIds.has(track.id)) {
994+
this.mediaControls.settings.addSubtitle(__('auto_generated'), idx);
995+
}
996+
});
997+
}
998+
} else {
999+
this.loadSubtitles();
1000+
}
1001+
} catch {
1002+
// Transcription is non-critical; allow the viewer to continue without it
1003+
}
1004+
}
1005+
9241006
/**
9251007
* Calculates the video dimension based on representations
9261008
*

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)