@@ -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 *
0 commit comments