From 03317ed45b1267a30d2b05dcd5bbac9a77a9af6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Malthe=20J=C3=B8rgensen?= Date: Sat, 13 Mar 2021 13:21:55 +0100 Subject: [PATCH 1/9] Add `MediaPlayer.addTextTrack`-method This method allows for loading captions from a file not listed in the manifest. --- index.d.ts | 1 + samples/captioning/external-caption-vtt.html | 43 ++++ samples/captioning/external-captions.vtt | 10 + samples/samples.json | 5 + src/core/errors/Errors.js | 3 + src/core/events/CoreEvents.js | 1 + src/streaming/CaptionsLoader.js | 200 +++++++++++++++++++ src/streaming/MediaPlayer.js | 53 +++++ 8 files changed, 316 insertions(+) create mode 100644 samples/captioning/external-caption-vtt.html create mode 100644 samples/captioning/external-captions.vtt create mode 100644 src/streaming/CaptionsLoader.js diff --git a/index.d.ts b/index.d.ts index 2dca98b4f6..017838a339 100644 --- a/index.d.ts +++ b/index.d.ts @@ -339,6 +339,7 @@ declare namespace dashjs { setQualityFor(type: MediaType, value: number): void; updatePortalSize(): void; enableText(enable: boolean): void; + addTextTrack(url: string): void; setTextTrack(idx: number): void; getTextDefaultLanguage(): string | undefined; setTextDefaultLanguage(lang: string): void; diff --git a/samples/captioning/external-caption-vtt.html b/samples/captioning/external-caption-vtt.html new file mode 100644 index 0000000000..20f112808a --- /dev/null +++ b/samples/captioning/external-caption-vtt.html @@ -0,0 +1,43 @@ + + + + + WebVTT Dash Demo + + + + + + + + + + + + +
+ +
+ + + \ No newline at end of file diff --git a/samples/captioning/external-captions.vtt b/samples/captioning/external-captions.vtt new file mode 100644 index 0000000000..b299738e4d --- /dev/null +++ b/samples/captioning/external-captions.vtt @@ -0,0 +1,10 @@ +WEBVTT + +00:00:00.500 --> 00:00:04.000 +These are example captions in the VTT format + +00:00:04.500 --> 00:00:08.300 +They are not listed in the manifest file + +00:00:010.000 --> 00:00:14.000 +This is the last caption. Enjoy! \ No newline at end of file diff --git a/samples/samples.json b/samples/samples.json index 4f21963df9..197c7cc311 100644 --- a/samples/samples.json +++ b/samples/samples.json @@ -154,6 +154,11 @@ "title": "TTML EBU timed text tracks", "description": "Example showing content with TTML EBU timed text tracks.", "href": "captioning/ttml-ebutt-sample.html" + }, + { + "title": "Load external VTT captions", + "description": "Example showing how to load external VTT captions.", + "href": "captioning/external-caption-vtt.html" } ] }, diff --git a/src/core/errors/Errors.js b/src/core/errors/Errors.js index c3485b5496..a43f0a7532 100644 --- a/src/core/errors/Errors.js +++ b/src/core/errors/Errors.js @@ -112,6 +112,9 @@ class Errors extends ErrorsBase { this.REMOVE_ERROR_MESSAGE = 'buffer is not defined'; this.DATA_UPDATE_FAILED_ERROR_MESSAGE = 'Data update failed'; + this.CAPTIONS_LOADER_PARSING_FAILURE_ERROR_MESSAGE = 'caption parsing failed for '; + this.CAPTIONS_LOADER_LOADING_FAILURE_ERROR_MESSAGE = 'Failed loading captions: '; + this.CAPABILITY_MEDIASOURCE_ERROR_MESSAGE = 'mediasource is not supported'; this.CAPABILITY_MEDIAKEYS_ERROR_MESSAGE = 'mediakeys is not supported'; this.TIMED_TEXT_ERROR_MESSAGE_PARSE = 'parsing error :'; diff --git a/src/core/events/CoreEvents.js b/src/core/events/CoreEvents.js index 2579f06f80..f2f4f7eb76 100644 --- a/src/core/events/CoreEvents.js +++ b/src/core/events/CoreEvents.js @@ -64,6 +64,7 @@ class CoreEvents extends EventsBase { this.MANIFEST_UPDATED = 'manifestUpdated'; this.MEDIA_FRAGMENT_LOADED = 'mediaFragmentLoaded'; this.MEDIA_FRAGMENT_NEEDED = 'mediaFragmentNeeded'; + this.EXTERNAL_CAPTIONS_LOADED = 'externalCaptionsLoaded'; this.QUOTA_EXCEEDED = 'quotaExceeded'; this.REPRESENTATION_UPDATE_STARTED = 'representationUpdateStarted'; this.REPRESENTATION_UPDATE_COMPLETED = 'representationUpdateCompleted'; diff --git a/src/streaming/CaptionsLoader.js b/src/streaming/CaptionsLoader.js new file mode 100644 index 0000000000..64e9015c2d --- /dev/null +++ b/src/streaming/CaptionsLoader.js @@ -0,0 +1,200 @@ +/** + * The copyright in this software is being made available under the BSD License, + * included below. This software may be subject to other third party and contributor + * rights, including patent rights, and no such rights are granted under this license. + * + * Copyright (c) 2013, Dash Industry Forum. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * Neither the name of Dash Industry Forum nor the names of its + * contributors may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +import Constants from './constants/Constants'; +import DashConstants from '../dash/constants/DashConstants'; +import URLLoader from './net/URLLoader'; +import URLUtils from './utils/URLUtils'; +import TextRequest from './vo/TextRequest'; +import DashJSError from './vo/DashJSError'; +import {HTTPRequest} from './vo/metrics/HTTPRequest'; +import EventBus from '../core/EventBus'; +import Events from '../core/events/Events'; +import Errors from '../core/errors/Errors'; +import FactoryMaker from '../core/FactoryMaker'; +import VTTParser from './utils/VTTParser'; + +function CaptionsLoader(config) { + + config = config || {}; + const context = this.context; + const debug = config.debug; + const eventBus = EventBus(context).getInstance(); + const urlUtils = URLUtils(context).getInstance(); + + let instance, + logger, + urlLoader, + parser; + + let mssHandler = config.mssHandler; + let errHandler = config.errHandler; + + function setup() { + logger = debug.getLogger(instance); + + urlLoader = URLLoader(context).create({ + errHandler: config.errHandler, + dashMetrics: config.dashMetrics, + mediaPlayerModel: config.mediaPlayerModel, + requestModifier: config.requestModifier, + useFetch: config.settings.get().streaming.lowLatencyEnabled, + urlUtils: urlUtils, + constants: Constants, + dashConstants: DashConstants, + errors: Errors + }); + } + + function createParser(data) { + if (data.indexOf('WEBVTT') > -1) { + return VTTParser(context).getInstance(); + } + // } else if (data.indexOf('MPD') > -1 || data.indexOf('Patch') > -1) { + // return DashParser(context).create({debug: debug}); + // } else { + // return parser; + // } + return null; + } + + function load(url) { + + const request = new TextRequest(url, HTTPRequest.MEDIA_SEGMENT_TYPE); + + urlLoader.load({ + request: request, + success: function (data, textStatus, responseURL) { + let actualUrl, + baseUri, + captions; + + // Handle redirects for the MPD - as per RFC3986 Section 5.1.3 + // also handily resolves relative MPD URLs to absolute + if (responseURL && responseURL !== url) { + baseUri = urlUtils.parseBaseUrl(responseURL); + actualUrl = responseURL; + } else { + // usually this case will be caught and resolved by + // responseURL above but it is not available for IE11 and Edge/12 and Edge/13 + // baseUri must be absolute for BaseURL resolution later + if (urlUtils.isRelative(url)) { + url = urlUtils.resolve(url, window.location.href); + } + + baseUri = urlUtils.parseBaseUrl(url); + } + + // A response of no content implies in-memory is properly up to date + if (textStatus == 'No Content') { + return; + } + + // Create parser according to captions type + parser = createParser(data); + + if (parser === null) { + eventBus.trigger(Events.EXTERNAL_CAPTIONS_LOADED, { + captions: null, + error: new DashJSError( + Errors.CAPTIONS_LOADER_PARSING_FAILURE_ERROR_CODE, + Errors.CAPTIONS_LOADER_PARSING_FAILURE_ERROR_MESSAGE + `${url}` + ) + }); + return; + } + + // const dataView = new DataView(bytes, 0, bytes.byteLength); + // ccContent = ISOBoxer.Utils.dataViewToString(dataView, Constants.UTF8); + try { + captions = parser.parse(data, 0); + } catch (e) { + errHandler.error(new DashJSError(Errors.TIMED_TEXT_ERROR_ID_PARSE_CODE, Errors.TIMED_TEXT_ERROR_MESSAGE_PARSE + e.message, data)); + } + // } catch (e) { + // eventBus.trigger(Events.EXTERNAL_CAPTIONS_LOADED, { + // captions: null, + // error: new DashJSError( + // Errors.CAPTIONS_LOADER_PARSING_FAILURE_ERROR_CODE, + // Errors.CAPTIONS_LOADER_PARSING_FAILURE_ERROR_MESSAGE + `${url}` + // ) + // }); + // return; + // } + + if (captions) { + eventBus.trigger(Events.EXTERNAL_CAPTIONS_LOADED, { captions: captions }); + } else { + eventBus.trigger(Events.EXTERNAL_CAPTIONS_LOADED, { + captions: null, + error: new DashJSError( + Errors.CAPTIONS_LOADER_PARSING_FAILURE_ERROR_CODE, + Errors.CAPTIONS_LOADER_PARSING_FAILURE_ERROR_MESSAGE + `${url}` + ) + }); + } + }, + error: function (request, statusText, errorText) { + eventBus.trigger(Events.EXTERNAL_CAPTIONS_LOADED, { + captions: null, + error: new DashJSError( + Errors.CAPTIONS_LOADER_LOADING_FAILURE_ERROR_CODE, + Errors.CAPTIONS_LOADER_LOADING_FAILURE_ERROR_MESSAGE + `${url}, ${errorText}` + ) + }); + } + }); + } + + function reset() { + if (urlLoader) { + urlLoader.abort(); + urlLoader = null; + } + + if (mssHandler) { + mssHandler.reset(); + } + } + + instance = { + load: load, + reset: reset + }; + + setup(); + + return instance; +} + +CaptionsLoader.__dashjs_factory_name = 'CaptionsLoader'; + +const factory = FactoryMaker.getClassFactory(CaptionsLoader); +export default factory; diff --git a/src/streaming/MediaPlayer.js b/src/streaming/MediaPlayer.js index b73cf1dc2c..b0c005680e 100644 --- a/src/streaming/MediaPlayer.js +++ b/src/streaming/MediaPlayer.js @@ -38,6 +38,7 @@ import GapController from './controllers/GapController'; import MediaController from './controllers/MediaController'; import BaseURLController from './controllers/BaseURLController'; import ManifestLoader from './ManifestLoader'; +import CaptionsLoader from './CaptionsLoader'; import ErrorHandler from './utils/ErrorHandler'; import Capabilities from './utils/Capabilities'; import CapabilitiesFilter from './utils/CapabilitiesFilter'; @@ -1288,6 +1289,45 @@ function MediaPlayer() { return textController.isTextEnabled(); } + /** + * Use this method to add a captions from an external URL (for captions not listed in the manifest). + * @param {string} url - URL of the captions file to load. Use module:MediaPlayer#dashjs.MediaPlayer.events.TEXT_TRACK_ADDED. + * @see {@link MediaPlayerEvents#event:TEXT_TRACK_ADDED dashjs.MediaPlayer.events.TEXT_TRACK_ADDED} + * @throws {@link module:MediaPlayer~PLAYBACK_NOT_INITIALIZED_ERROR PLAYBACK_NOT_INITIALIZED_ERROR} if called before initializePlayback function + * @memberof module:MediaPlayer + * @instance + */ + function addTextTrack(url) { + if (!streamingInitialized) { + throw STREAMING_NOT_INITIALIZED_ERROR; + } + + if (textController === undefined) { + textController = TextController(context).getInstance(); + } + + /********* END ***********/ + let captionsLoader = createCaptionsLoader(); + let self = this; + + const handler = function (e) { + if (!e.error) { + let textTracks = TextTracks(context).getInstance(); + textTracks.addCaptions(textTracks.getCurrentTrackIdx(), 0, e.captions); + } else { + // ???? + } + eventBus.off(Events.EXTERNAL_CAPTIONS_LOADED, handler, self); + captionsLoader.reset(); + }; + + eventBus.on(Events.EXTERNAL_CAPTIONS_LOADED, handler, self); + + uriFragmentModel.initialize(url); + captionsLoader.load(url); + /********* END ***********/ + } + /** * Use this method to change the current text track for both external time text files and fragmented text tracks. There is no need to * set the track mode on the video object to switch a track when using this method. @@ -2159,6 +2199,18 @@ function MediaPlayer() { }); } + function createCaptionsLoader() { + return CaptionsLoader(context).create({ + debug: debug, + errHandler: errHandler, + dashMetrics: dashMetrics, + mediaPlayerModel: mediaPlayerModel, + requestModifier: RequestModifier(context).getInstance(), + mssHandler: mssHandler, + settings: settings + }); + } + function detectProtection() { if (protectionController) { return protectionController; @@ -2405,6 +2457,7 @@ function MediaPlayer() { enableText: enableText, enableForcedTextStreaming: enableForcedTextStreaming, isTextEnabled: isTextEnabled, + addTextTrack: addTextTrack, setTextTrack: setTextTrack, getBitrateInfoListFor: getBitrateInfoListFor, getStreamsFromManifest: getStreamsFromManifest, From b1672ba2b54460a471c178c9c1d2758a89ac14b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Malthe=20J=C3=B8rgensen?= Date: Sat, 13 Mar 2021 14:17:29 +0100 Subject: [PATCH 2/9] Don't override current captions -- add a new track --- src/streaming/MediaPlayer.js | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/streaming/MediaPlayer.js b/src/streaming/MediaPlayer.js index b0c005680e..2752a541dd 100644 --- a/src/streaming/MediaPlayer.js +++ b/src/streaming/MediaPlayer.js @@ -43,6 +43,7 @@ import ErrorHandler from './utils/ErrorHandler'; import Capabilities from './utils/Capabilities'; import CapabilitiesFilter from './utils/CapabilitiesFilter'; import TextTracks from './text/TextTracks'; +import TextTrackInfo from './vo/TextTrackInfo'; import RequestModifier from './utils/RequestModifier'; import TextController from './text/TextController'; import URIFragmentModel from './models/URIFragmentModel'; @@ -1313,7 +1314,40 @@ function MediaPlayer() { const handler = function (e) { if (!e.error) { let textTracks = TextTracks(context).getInstance(); - textTracks.addCaptions(textTracks.getCurrentTrackIdx(), 0, e.captions); + + const textTrackInfo = new TextTrackInfo(); + const trackKindMap = { subtitle: 'subtitles', caption: 'captions' }; //Dash Spec has no "s" on end of KIND but HTML needs plural. + const getKind = function () { + let kind = (mediaInfo.roles.length > 0) ? trackKindMap[mediaInfo.roles[0]] : trackKindMap.caption; + kind = (kind === trackKindMap.caption || kind === trackKindMap.subtitle) ? kind : trackKindMap.caption; + return kind; + }; + + const checkTTML = function () { + let ttml = false; + if (mediaInfo.codec && mediaInfo.codec.search(Constants.STPP) >= 0) { + ttml = true; + } + if (mediaInfo.mimeType && mediaInfo.mimeType.search(Constants.TTML) >= 0) { + ttml = true; + } + return ttml; + }; + + textTrackInfo.captionData = e.captions; + textTrackInfo.lang = 'en-US'; // mediaInfo.lang; + textTrackInfo.labels = ['malthe']; // mediaInfo.labels; + textTrackInfo.id = 1; // mediaInfo.id ? mediaInfo.id : mediaInfo.index; // AdaptationSet id (an unsigned int) as it's optional parameter, use mediaInfo.index + textTrackInfo.index = 1; // mediaInfo.index; // AdaptationSet index in manifest + textTrackInfo.isTTML = false; // checkTTML(); + textTrackInfo.defaultTrack = false; // getIsDefault(mediaInfo); + textTrackInfo.isFragmented = false; // !adapter.getIsTextTrack(mediaInfo.mimeType); + textTrackInfo.isEmbedded = false; // mediaInfo.isEmbedded ? true : false; + textTrackInfo.kind = trackKindMap.caption; // getKind(); + // textTrackInfo.roles = mediaInfo.roles; + // textTrackInfo.accessibility = mediaInfo.accessibility; + const totalNrTracks = 2; // (mediaInfos ? mediaInfos.length : 0) + embeddedTracks.length; + textTracks.addTextTrack(textTrackInfo, totalNrTracks); } else { // ???? } From 400c5a81f12ececf1f364696ede5f08e8375ab4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Malthe=20J=C3=B8rgensen?= Date: Tue, 16 Mar 2021 10:48:12 +0100 Subject: [PATCH 3/9] addTextTrack: Add `mediaInfo`-argument --- index.d.ts | 2 +- samples/captioning/external-caption-vtt.html | 20 ++++++++++++++++++- src/streaming/MediaPlayer.js | 21 ++++++++++---------- 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/index.d.ts b/index.d.ts index 017838a339..aaf8b6cf99 100644 --- a/index.d.ts +++ b/index.d.ts @@ -339,7 +339,7 @@ declare namespace dashjs { setQualityFor(type: MediaType, value: number): void; updatePortalSize(): void; enableText(enable: boolean): void; - addTextTrack(url: string): void; + addTextTrack(url: string, mediaInfo: MediaInfo): void; setTextTrack(idx: number): void; getTextDefaultLanguage(): string | undefined; setTextDefaultLanguage(lang: string): void; diff --git a/samples/captioning/external-caption-vtt.html b/samples/captioning/external-caption-vtt.html index 20f112808a..2791431ea7 100644 --- a/samples/captioning/external-caption-vtt.html +++ b/samples/captioning/external-caption-vtt.html @@ -21,8 +21,26 @@ player.initialize(video, url, true); player.setTextDefaultEnabled(true); + var mediaInfo = { + id: 1, + lang: 'en-US', + index: null, + type: null, + streamInfo: null, + representationCount: 0, + viewpoint: null, + roles: [], + codec: null, + mimeType: null, + contentProtection: null, + isText: true, + KID: null, + bitrateList: null, + } + player.on(dashjs.MediaPlayer.events['STREAM_INITIALIZED'], function handleEvent(e) { - player.addTextTrack('http://localhost:3001/samples/captioning/external-captions.vtt'); + player.addTextTrack('http://localhost:3001/samples/captioning/external-captions.vtt', mediaInfo); + player.setTextTrack(2); }); } diff --git a/src/streaming/MediaPlayer.js b/src/streaming/MediaPlayer.js index 2752a541dd..27643cedd6 100644 --- a/src/streaming/MediaPlayer.js +++ b/src/streaming/MediaPlayer.js @@ -1293,12 +1293,13 @@ function MediaPlayer() { /** * Use this method to add a captions from an external URL (for captions not listed in the manifest). * @param {string} url - URL of the captions file to load. Use module:MediaPlayer#dashjs.MediaPlayer.events.TEXT_TRACK_ADDED. + * @param {MediaInfo} mediaInfo - A MediaInfo object with details about the text track. * @see {@link MediaPlayerEvents#event:TEXT_TRACK_ADDED dashjs.MediaPlayer.events.TEXT_TRACK_ADDED} * @throws {@link module:MediaPlayer~PLAYBACK_NOT_INITIALIZED_ERROR PLAYBACK_NOT_INITIALIZED_ERROR} if called before initializePlayback function * @memberof module:MediaPlayer * @instance */ - function addTextTrack(url) { + function addTextTrack(url, mediaInfo) { if (!streamingInitialized) { throw STREAMING_NOT_INITIALIZED_ERROR; } @@ -1335,17 +1336,17 @@ function MediaPlayer() { }; textTrackInfo.captionData = e.captions; - textTrackInfo.lang = 'en-US'; // mediaInfo.lang; - textTrackInfo.labels = ['malthe']; // mediaInfo.labels; - textTrackInfo.id = 1; // mediaInfo.id ? mediaInfo.id : mediaInfo.index; // AdaptationSet id (an unsigned int) as it's optional parameter, use mediaInfo.index - textTrackInfo.index = 1; // mediaInfo.index; // AdaptationSet index in manifest - textTrackInfo.isTTML = false; // checkTTML(); + textTrackInfo.lang = mediaInfo.lang; + textTrackInfo.labels = mediaInfo.labels; + textTrackInfo.id = mediaInfo.id ? mediaInfo.id : mediaInfo.index; // AdaptationSet id (an unsigned int) as it's optional parameter, use mediaInfo.index + textTrackInfo.index = mediaInfo.index; // AdaptationSet index in manifest + textTrackInfo.isTTML = checkTTML(); textTrackInfo.defaultTrack = false; // getIsDefault(mediaInfo); textTrackInfo.isFragmented = false; // !adapter.getIsTextTrack(mediaInfo.mimeType); - textTrackInfo.isEmbedded = false; // mediaInfo.isEmbedded ? true : false; - textTrackInfo.kind = trackKindMap.caption; // getKind(); - // textTrackInfo.roles = mediaInfo.roles; - // textTrackInfo.accessibility = mediaInfo.accessibility; + textTrackInfo.isEmbedded = mediaInfo.isEmbedded ? true : false; + textTrackInfo.kind = getKind(); + textTrackInfo.roles = mediaInfo.roles; + textTrackInfo.accessibility = mediaInfo.accessibility; const totalNrTracks = 2; // (mediaInfos ? mediaInfos.length : 0) + embeddedTracks.length; textTracks.addTextTrack(textTrackInfo, totalNrTracks); } else { From b32c526bdf8fd83978d11e88e4ea8bf0a4d046f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Malthe=20J=C3=B8rgensen?= Date: Tue, 16 Mar 2021 10:56:10 +0100 Subject: [PATCH 4/9] Add TextTracks.getNumberOfTextTracks() Needed for MediaPlayer.addTextTrack() --- src/streaming/MediaPlayer.js | 2 +- src/streaming/text/TextTracks.js | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/streaming/MediaPlayer.js b/src/streaming/MediaPlayer.js index 27643cedd6..69ae7ca59f 100644 --- a/src/streaming/MediaPlayer.js +++ b/src/streaming/MediaPlayer.js @@ -1347,7 +1347,7 @@ function MediaPlayer() { textTrackInfo.kind = getKind(); textTrackInfo.roles = mediaInfo.roles; textTrackInfo.accessibility = mediaInfo.accessibility; - const totalNrTracks = 2; // (mediaInfos ? mediaInfos.length : 0) + embeddedTracks.length; + const totalNrTracks = textTracks.getNumberOfTextTracks() + 1; textTracks.addTextTrack(textTrackInfo, totalNrTracks); } else { // ???? diff --git a/src/streaming/text/TextTracks.js b/src/streaming/text/TextTracks.js index 13ecf889e5..06577dc42d 100644 --- a/src/streaming/text/TextTracks.js +++ b/src/streaming/text/TextTracks.js @@ -549,6 +549,10 @@ function TextTracks() { } } + function getNumberOfTextTracks() { + return trackElementArr.length; + } + function setCueStyleOnTrack(track) { clearCaptionContainer.call(this); if (track) { @@ -684,6 +688,7 @@ function TextTracks() { addCaptions: addCaptions, getCurrentTrackIdx: getCurrentTrackIdx, setCurrentTrackIdx: setCurrentTrackIdx, + getNumberOfTextTracks: getNumberOfTextTracks, getTrackIdxForId: getTrackIdxForId, getCurrentTrackInfo: getCurrentTrackInfo, setModeForTrackIdx: setModeForTrackIdx, From 599d31a2a0c73eb7b7afc02311efbdb3ea1bee8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Malthe=20J=C3=B8rgensen?= Date: Tue, 16 Mar 2021 11:12:42 +0100 Subject: [PATCH 5/9] Remove unused comments --- src/streaming/CaptionsLoader.js | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/streaming/CaptionsLoader.js b/src/streaming/CaptionsLoader.js index 64e9015c2d..c94d75f866 100644 --- a/src/streaming/CaptionsLoader.js +++ b/src/streaming/CaptionsLoader.js @@ -131,23 +131,11 @@ function CaptionsLoader(config) { return; } - // const dataView = new DataView(bytes, 0, bytes.byteLength); - // ccContent = ISOBoxer.Utils.dataViewToString(dataView, Constants.UTF8); try { captions = parser.parse(data, 0); } catch (e) { errHandler.error(new DashJSError(Errors.TIMED_TEXT_ERROR_ID_PARSE_CODE, Errors.TIMED_TEXT_ERROR_MESSAGE_PARSE + e.message, data)); } - // } catch (e) { - // eventBus.trigger(Events.EXTERNAL_CAPTIONS_LOADED, { - // captions: null, - // error: new DashJSError( - // Errors.CAPTIONS_LOADER_PARSING_FAILURE_ERROR_CODE, - // Errors.CAPTIONS_LOADER_PARSING_FAILURE_ERROR_MESSAGE + `${url}` - // ) - // }); - // return; - // } if (captions) { eventBus.trigger(Events.EXTERNAL_CAPTIONS_LOADED, { captions: captions }); From e369a9bf8b755c3b1382446bc1e92ae2f33c3bef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Malthe=20J=C3=B8rgensen?= Date: Tue, 16 Mar 2021 11:17:45 +0100 Subject: [PATCH 6/9] CaptionsLoader: Remove unused `baseUri` --- src/streaming/CaptionsLoader.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/streaming/CaptionsLoader.js b/src/streaming/CaptionsLoader.js index c94d75f866..62dae0830b 100644 --- a/src/streaming/CaptionsLoader.js +++ b/src/streaming/CaptionsLoader.js @@ -93,23 +93,18 @@ function CaptionsLoader(config) { request: request, success: function (data, textStatus, responseURL) { let actualUrl, - baseUri, captions; // Handle redirects for the MPD - as per RFC3986 Section 5.1.3 // also handily resolves relative MPD URLs to absolute if (responseURL && responseURL !== url) { - baseUri = urlUtils.parseBaseUrl(responseURL); actualUrl = responseURL; } else { // usually this case will be caught and resolved by // responseURL above but it is not available for IE11 and Edge/12 and Edge/13 - // baseUri must be absolute for BaseURL resolution later if (urlUtils.isRelative(url)) { url = urlUtils.resolve(url, window.location.href); } - - baseUri = urlUtils.parseBaseUrl(url); } // A response of no content implies in-memory is properly up to date From e65cc5649b0818133cf18401b95e6324e8ac8a74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Malthe=20J=C3=B8rgensen?= Date: Tue, 16 Mar 2021 11:18:09 +0100 Subject: [PATCH 7/9] external-caption-vtt sample: Use relative URL --- samples/captioning/external-caption-vtt.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/captioning/external-caption-vtt.html b/samples/captioning/external-caption-vtt.html index 2791431ea7..c307ddcb08 100644 --- a/samples/captioning/external-caption-vtt.html +++ b/samples/captioning/external-caption-vtt.html @@ -39,8 +39,8 @@ } player.on(dashjs.MediaPlayer.events['STREAM_INITIALIZED'], function handleEvent(e) { - player.addTextTrack('http://localhost:3001/samples/captioning/external-captions.vtt', mediaInfo); - player.setTextTrack(2); + // Relative URL used here -- absolute URLs are also supported + player.addTextTrack('external-captions.vtt', mediaInfo); }); } From 97006dbd0ebd00b9b76ea0087921f0c54c418f8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Malthe=20J=C3=B8rgensen?= Date: Fri, 2 Apr 2021 16:27:51 +0200 Subject: [PATCH 8/9] CaptionsLoader.js: Remove unused comment --- src/streaming/CaptionsLoader.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/streaming/CaptionsLoader.js b/src/streaming/CaptionsLoader.js index 62dae0830b..ce85535a83 100644 --- a/src/streaming/CaptionsLoader.js +++ b/src/streaming/CaptionsLoader.js @@ -77,11 +77,6 @@ function CaptionsLoader(config) { if (data.indexOf('WEBVTT') > -1) { return VTTParser(context).getInstance(); } - // } else if (data.indexOf('MPD') > -1 || data.indexOf('Patch') > -1) { - // return DashParser(context).create({debug: debug}); - // } else { - // return parser; - // } return null; } From 39773bba3849b7c361447b4d6229b641ae21f18d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Malthe=20J=C3=B8rgensen?= Date: Fri, 2 Apr 2021 23:07:10 +0200 Subject: [PATCH 9/9] External captions: Reuse `TextSourceBuffer.createTextTrackFromMediaInfo` Move `EXTERNAL_CAPTIONS_LOADED`-event listener into TextSourceBuffer and to allow reusing `createTextTrackFromMediaInfo`. This requires passing `mediaInfo` to CaptionsLoader and adding the passed MediaInfo to `TextSourceBuffer.mediaInfos`. Also removes `TextTracks.getNumberOfTextTracks()` which is no longer needed. --- src/streaming/CaptionsLoader.js | 4 +- src/streaming/MediaPlayer.js | 55 ++------------------------ src/streaming/text/TextSourceBuffer.js | 14 +++++++ src/streaming/text/TextTracks.js | 5 --- 4 files changed, 20 insertions(+), 58 deletions(-) diff --git a/src/streaming/CaptionsLoader.js b/src/streaming/CaptionsLoader.js index ce85535a83..c42b0d4349 100644 --- a/src/streaming/CaptionsLoader.js +++ b/src/streaming/CaptionsLoader.js @@ -80,7 +80,7 @@ function CaptionsLoader(config) { return null; } - function load(url) { + function load(url, mediaInfo) { const request = new TextRequest(url, HTTPRequest.MEDIA_SEGMENT_TYPE); @@ -128,7 +128,7 @@ function CaptionsLoader(config) { } if (captions) { - eventBus.trigger(Events.EXTERNAL_CAPTIONS_LOADED, { captions: captions }); + eventBus.trigger(Events.EXTERNAL_CAPTIONS_LOADED, { captions: captions, mediaInfo: mediaInfo }); } else { eventBus.trigger(Events.EXTERNAL_CAPTIONS_LOADED, { captions: null, diff --git a/src/streaming/MediaPlayer.js b/src/streaming/MediaPlayer.js index 69ae7ca59f..8599bbe284 100644 --- a/src/streaming/MediaPlayer.js +++ b/src/streaming/MediaPlayer.js @@ -43,7 +43,6 @@ import ErrorHandler from './utils/ErrorHandler'; import Capabilities from './utils/Capabilities'; import CapabilitiesFilter from './utils/CapabilitiesFilter'; import TextTracks from './text/TextTracks'; -import TextTrackInfo from './vo/TextTrackInfo'; import RequestModifier from './utils/RequestModifier'; import TextController from './text/TextController'; import URIFragmentModel from './models/URIFragmentModel'; @@ -1308,59 +1307,13 @@ function MediaPlayer() { textController = TextController(context).getInstance(); } - /********* END ***********/ let captionsLoader = createCaptionsLoader(); - let self = this; - - const handler = function (e) { - if (!e.error) { - let textTracks = TextTracks(context).getInstance(); - - const textTrackInfo = new TextTrackInfo(); - const trackKindMap = { subtitle: 'subtitles', caption: 'captions' }; //Dash Spec has no "s" on end of KIND but HTML needs plural. - const getKind = function () { - let kind = (mediaInfo.roles.length > 0) ? trackKindMap[mediaInfo.roles[0]] : trackKindMap.caption; - kind = (kind === trackKindMap.caption || kind === trackKindMap.subtitle) ? kind : trackKindMap.caption; - return kind; - }; - - const checkTTML = function () { - let ttml = false; - if (mediaInfo.codec && mediaInfo.codec.search(Constants.STPP) >= 0) { - ttml = true; - } - if (mediaInfo.mimeType && mediaInfo.mimeType.search(Constants.TTML) >= 0) { - ttml = true; - } - return ttml; - }; - - textTrackInfo.captionData = e.captions; - textTrackInfo.lang = mediaInfo.lang; - textTrackInfo.labels = mediaInfo.labels; - textTrackInfo.id = mediaInfo.id ? mediaInfo.id : mediaInfo.index; // AdaptationSet id (an unsigned int) as it's optional parameter, use mediaInfo.index - textTrackInfo.index = mediaInfo.index; // AdaptationSet index in manifest - textTrackInfo.isTTML = checkTTML(); - textTrackInfo.defaultTrack = false; // getIsDefault(mediaInfo); - textTrackInfo.isFragmented = false; // !adapter.getIsTextTrack(mediaInfo.mimeType); - textTrackInfo.isEmbedded = mediaInfo.isEmbedded ? true : false; - textTrackInfo.kind = getKind(); - textTrackInfo.roles = mediaInfo.roles; - textTrackInfo.accessibility = mediaInfo.accessibility; - const totalNrTracks = textTracks.getNumberOfTextTracks() + 1; - textTracks.addTextTrack(textTrackInfo, totalNrTracks); - } else { - // ???? - } - eventBus.off(Events.EXTERNAL_CAPTIONS_LOADED, handler, self); - captionsLoader.reset(); - }; - - eventBus.on(Events.EXTERNAL_CAPTIONS_LOADED, handler, self); uriFragmentModel.initialize(url); - captionsLoader.load(url); - /********* END ***********/ + + // The event `EXTERNAL_CAPTIONS_LOADED` is handled by TextSourceBuffer + // and does the required work to add the track once it's loaded. + captionsLoader.load(url, mediaInfo); } /** diff --git a/src/streaming/text/TextSourceBuffer.js b/src/streaming/text/TextSourceBuffer.js index e6084d7ab3..04cea8a025 100644 --- a/src/streaming/text/TextSourceBuffer.js +++ b/src/streaming/text/TextSourceBuffer.js @@ -81,6 +81,8 @@ function TextSourceBuffer() { logger = Debug(context).getInstance().getLogger(instance); resetInitialSettings(); + + eventBus.on(Events.EXTERNAL_CAPTIONS_LOADED, onExternalCaptionsLoaded, instance); } function resetFragmented () { @@ -265,6 +267,18 @@ function TextSourceBuffer() { currFragmentedTrackIdx = idx; } + + function onExternalCaptionsLoaded(e) { + if (!e.error) { + // Extend `mediaInfos`-array to ensure `totalNrTracks` is increased. + mediaInfos = mediaInfos.concat([e.mediaInfo]); + createTextTrackFromMediaInfo(e.captions, e.mediaInfo); + } else { + logger.error('Unable to load external captions: ' + e.error.message); + } + eventBus.off(Events.EXTERNAL_CAPTIONS_LOADED, onExternalCaptionsLoaded, this); + } + function createTextTrackFromMediaInfo(captionData, mediaInfo) { const textTrackInfo = new TextTrackInfo(); const trackKindMap = { subtitle: 'subtitles', caption: 'captions' }; //Dash Spec has no "s" on end of KIND but HTML needs plural. diff --git a/src/streaming/text/TextTracks.js b/src/streaming/text/TextTracks.js index 06577dc42d..13ecf889e5 100644 --- a/src/streaming/text/TextTracks.js +++ b/src/streaming/text/TextTracks.js @@ -549,10 +549,6 @@ function TextTracks() { } } - function getNumberOfTextTracks() { - return trackElementArr.length; - } - function setCueStyleOnTrack(track) { clearCaptionContainer.call(this); if (track) { @@ -688,7 +684,6 @@ function TextTracks() { addCaptions: addCaptions, getCurrentTrackIdx: getCurrentTrackIdx, setCurrentTrackIdx: setCurrentTrackIdx, - getNumberOfTextTracks: getNumberOfTextTracks, getTrackIdxForId: getTrackIdxForId, getCurrentTrackInfo: getCurrentTrackInfo, setModeForTrackIdx: setModeForTrackIdx,