@l5i/dashjs
Version:
A reference client implementation for the playback of MPEG DASH via Javascript and compliant browsers.
334 lines (295 loc) • 12.7 kB
JavaScript
/**
* 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 FactoryMaker from '../../core/FactoryMaker';
import TextSourceBuffer from './TextSourceBuffer';
import TextTracks from './TextTracks';
import VTTParser from '../utils/VTTParser';
import TTMLParser from '../utils/TTMLParser';
import EventBus from '../../core/EventBus';
import Events from '../../core/events/Events';
function TextController() {
let context = this.context;
let instance;
let textSourceBuffer;
let errHandler,
dashManifestModel,
manifestModel,
mediaController,
videoModel,
streamController,
textTracks,
vttParser,
ttmlParser,
eventBus,
defaultLanguage,
lastEnabledIndex,
textDefaultEnabled, // this is used for default settings (each time a file is loaded, we check value of this settings )
allTracksAreDisabled, // this is used for one session (when a file has been loaded, we use this settings to enable/disable text)
forceTextStreaming;
function setup() {
defaultLanguage = '';
lastEnabledIndex = -1;
textDefaultEnabled = true;
forceTextStreaming = false;
textTracks = TextTracks(context).getInstance();
vttParser = VTTParser(context).getInstance();
ttmlParser = TTMLParser(context).getInstance();
textSourceBuffer = TextSourceBuffer(context).getInstance();
eventBus = EventBus(context).getInstance();
textTracks.initialize();
eventBus.on(Events.TEXT_TRACKS_QUEUE_INITIALIZED, onTextTracksAdded, instance);
resetInitialSettings();
}
function setConfig(config) {
if (!config) {
return;
}
if (config.errHandler) {
errHandler = config.errHandler;
}
if (config.dashManifestModel) {
dashManifestModel = config.dashManifestModel;
}
if (config.manifestModel) {
manifestModel = config.manifestModel;
}
if (config.mediaController) {
mediaController = config.mediaController;
}
if (config.videoModel) {
videoModel = config.videoModel;
}
if (config.streamController) {
streamController = config.streamController;
}
if (config.textTracks) {
textTracks = config.textTracks;
}
if (config.vttParser) {
vttParser = config.vttParser;
}
if (config.ttmlParser) {
ttmlParser = config.ttmlParser;
}
// create config for source buffer
textSourceBuffer.setConfig({
errHandler: errHandler,
dashManifestModel: dashManifestModel,
manifestModel: manifestModel,
mediaController: mediaController,
videoModel: videoModel,
streamController: streamController,
textTracks: textTracks,
vttParser: vttParser,
ttmlParser: ttmlParser
});
}
function getTextSourceBuffer() {
return textSourceBuffer;
}
function getAllTracksAreDisabled() {
return allTracksAreDisabled;
}
function addEmbeddedTrack(mediaInfo) {
textSourceBuffer.addEmbeddedTrack(mediaInfo);
}
function setTextDefaultLanguage(lang) {
if (typeof lang !== 'string') {
return;
}
defaultLanguage = lang;
}
function getTextDefaultLanguage() {
return defaultLanguage;
}
function onTextTracksAdded(e) {
let tracks = e.tracks;
let index = e.index;
tracks.some((item, idx) => {
if (item.lang === defaultLanguage) {
this.setTextTrack(idx);
index = idx;
return true;
}
});
if (!textDefaultEnabled) {
// disable text at startup
this.setTextTrack(-1);
}
lastEnabledIndex = index;
eventBus.trigger(Events.TEXT_TRACKS_ADDED, {
enabled: isTextEnabled(),
index: index,
tracks: tracks
});
}
function setTextDefaultEnabled(enable) {
if (typeof enable !== 'boolean') {
return;
}
textDefaultEnabled = enable;
if (!textDefaultEnabled) {
// disable text at startup
this.setTextTrack(-1);
}
}
function getTextDefaultEnabled() {
return textDefaultEnabled;
}
function enableText(enable) {
if (typeof enable !== 'boolean') {
return;
}
if (isTextEnabled() !== enable) {
// change track selection
if (enable) {
// apply last enabled tractk
this.setTextTrack(lastEnabledIndex);
}
if (!enable) {
// keep last index and disable text track
lastEnabledIndex = this.getCurrentTrackIdx();
this.setTextTrack(-1);
}
}
}
function isTextEnabled() {
let enabled = true;
if (allTracksAreDisabled && !forceTextStreaming) {
enabled = false;
}
return enabled;
}
// when set to true NextFragmentRequestRule will allow schedule of chunks even if tracks are all disabled. Allowing streaming to hidden track for external players to work with.
function enableForcedTextStreaming(enable) {
if (typeof enable !== 'boolean') {
return;
}
forceTextStreaming = enable;
}
function setTextTrack(idx) {
//For external time text file, the only action needed to change a track is marking the track mode to showing.
// Fragmented text tracks need the additional step of calling TextController.setTextTrack();
let config = textSourceBuffer.getConfig();
let fragmentModel = config.fragmentModel;
let fragmentedTracks = config.fragmentedTracks;
let videoModel = config.videoModel;
let mediaInfosArr,
streamProcessor;
allTracksAreDisabled = idx === -1 ? true : false;
let oldTrackIdx = textTracks.getCurrentTrackIdx();
if (oldTrackIdx !== idx) {
textTracks.setModeForTrackIdx(oldTrackIdx, Constants.TEXT_HIDDEN);
textTracks.setCurrentTrackIdx(idx);
textTracks.setModeForTrackIdx(idx, Constants.TEXT_SHOWING);
let currentTrackInfo = textTracks.getCurrentTrackInfo();
if (currentTrackInfo && currentTrackInfo.isFragmented && !currentTrackInfo.isEmbedded) {
for (let i = 0; i < fragmentedTracks.length; i++) {
let mediaInfo = fragmentedTracks[i];
if (currentTrackInfo.lang === mediaInfo.lang && currentTrackInfo.index === mediaInfo.index &&
(mediaInfo.id ? currentTrackInfo.label === mediaInfo.id : currentTrackInfo.label === mediaInfo.index)) {
let currentFragTrack = mediaController.getCurrentTrackFor(Constants.FRAGMENTED_TEXT, streamController.getActiveStreamInfo());
if (mediaInfo !== currentFragTrack) {
fragmentModel.abortRequests();
fragmentModel.removeExecutedRequestsBeforeTime();
textSourceBuffer.remove();
textTracks.deleteCuesFromTrackIdx(oldTrackIdx);
mediaController.setTrack(mediaInfo);
textSourceBuffer.setCurrentFragmentedTrackIdx(i);
} else if (oldTrackIdx === -1) {
//in fragmented use case, if the user selects the older track (the one selected before disabled text track)
//no CURRENT_TRACK_CHANGED event will be trigger, so dashHandler current time has to be updated and the scheduleController
//has to be restarted.
const streamProcessors = streamController.getActiveStreamProcessors();
for (let i = 0; i < streamProcessors.length; i++) {
if (streamProcessors[i].getType() === Constants.FRAGMENTED_TEXT) {
streamProcessor = streamProcessors[i];
break;
}
}
streamProcessor.getIndexHandler().setCurrentTime(videoModel.getTime());
streamProcessor.getScheduleController().start();
}
}
}
} else if (currentTrackInfo && !currentTrackInfo.isFragmented) {
const streamProcessors = streamController.getActiveStreamProcessors();
for (let i = 0; i < streamProcessors.length; i++) {
if (streamProcessors[i].getType() === Constants.TEXT) {
streamProcessor = streamProcessors[i];
mediaInfosArr = streamProcessor.getMediaInfoArr();
break;
}
}
if (streamProcessor && mediaInfosArr) {
for (let i = 0; i < mediaInfosArr.length; i++) {
if (mediaInfosArr[i].index === currentTrackInfo.index && mediaInfosArr[i].lang === currentTrackInfo.lang) {
streamProcessor.selectMediaInfo(mediaInfosArr[i]);
break;
}
}
}
}
}
}
function getCurrentTrackIdx() {
return textTracks.getCurrentTrackIdx();
}
function resetInitialSettings() {
allTracksAreDisabled = false;
}
function reset() {
resetInitialSettings();
textSourceBuffer.resetEmbedded();
textSourceBuffer.reset();
}
instance = {
setConfig: setConfig,
getTextSourceBuffer: getTextSourceBuffer,
getAllTracksAreDisabled: getAllTracksAreDisabled,
addEmbeddedTrack: addEmbeddedTrack,
getTextDefaultLanguage: getTextDefaultLanguage,
setTextDefaultLanguage: setTextDefaultLanguage,
setTextDefaultEnabled: setTextDefaultEnabled,
getTextDefaultEnabled: getTextDefaultEnabled,
enableText: enableText,
isTextEnabled: isTextEnabled,
setTextTrack: setTextTrack,
getCurrentTrackIdx: getCurrentTrackIdx,
enableForcedTextStreaming: enableForcedTextStreaming,
reset: reset
};
setup();
return instance;
}
TextController.__dashjs_factory_name = 'TextController';
export default FactoryMaker.getSingletonFactory(TextController);