From 4220a0bbf72b468366e7a487ac58f061910a932d Mon Sep 17 00:00:00 2001 From: Matthew Neil Date: Thu, 23 Feb 2017 14:26:23 -0500 Subject: [PATCH 01/50] added vtt playlist loader --- src/master-playlist-controller.js | 223 +++++++++++++++++++++++++++++- src/playlist-loader.js | 44 ++++-- src/videojs-contrib-hls.js | 6 + 3 files changed, 254 insertions(+), 19 deletions(-) diff --git a/src/master-playlist-controller.js b/src/master-playlist-controller.js index 25342d6c3..93892c7e9 100644 --- a/src/master-playlist-controller.js +++ b/src/master-playlist-controller.js @@ -231,13 +231,13 @@ export class MasterPlaylistController extends videojs.EventTarget { this.cueTagsTrack_.inBandMetadataTrackDispatchType = ''; } - this.audioTracks_ = []; this.requestOptions_ = { withCredentials: this.withCredentials, timeout: null }; this.audioGroups_ = {}; + this.subtitleGroups_ = { groups: {}, tracks: {} }; this.mediaSource = new videojs.MediaSource({ mode }); this.audioinfo_ = null; @@ -274,6 +274,7 @@ export class MasterPlaylistController extends videojs.EventTarget { this.masterPlaylistLoader_ = new PlaylistLoader(url, this.hls_, this.withCredentials); this.setupMasterPlaylistLoaderListeners_(); this.audioPlaylistLoader_ = null; + this.subtitlePlaylistLoader_ = null; // setup segment loaders // combined audio/video or just video when alternate audio track is selected @@ -289,12 +290,11 @@ export class MasterPlaylistController extends videojs.EventTarget { this.setupSegmentLoaderListeners_(); - this.masterPlaylistLoader_.start(); - // Create SegmentLoader stat-getters loaderStats.forEach((stat) => { this[stat + '_'] = sumLoaderStat.bind(this, stat); }); + this.masterPlaylistLoader_.load(); } /** @@ -326,6 +326,9 @@ export class MasterPlaylistController extends videojs.EventTarget { this.fillAudioTracks_(); this.setupAudio(); + this.fillSubtitleTracks_(); + this.setupSubtitles(); + try { this.setupSourceBuffers_(); } catch (e) { @@ -335,6 +338,7 @@ export class MasterPlaylistController extends videojs.EventTarget { this.setupFirstPlay(); this.trigger('audioupdate'); + this.trigger('subtitleupdate'); this.trigger('selectedinitialmedia'); }); @@ -576,6 +580,111 @@ export class MasterPlaylistController extends videojs.EventTarget { return kind; } + /** + * fill our internal list of Subtitle Tracks with data from + * the master playlist or use a default + * + * @private + */ + fillSubtitleTracks_() { + let master = this.master(); + let mediaGroups = master.mediaGroups || {}; + + // force a default if we have none + // TODO: Determine if this needs to be done + if (!mediaGroups || + !mediaGroups.SUBTITLES || + Object.keys(mediaGroups.SUBTITLES).length === 0) { + // "main" subtitle group, track name "default" + mediaGroups.SUBTITLES = { main: { default: { default: true } } }; + } + + for (let mediaGroup in mediaGroups.SUBTITLES) { + if (!this.subtitleGroups_.groups[mediaGroup]) { + // this.subtitleGroups_.groups[mediaGroup] = { unforced: [], forced: {} }; + this.subtitleGroups_.groups[mediaGroup] = []; + } + + for (let label in mediaGroups.SUBTITLES[mediaGroup]) { + let properties = mediaGroups.SUBTITLES[mediaGroup][label]; + + // if (properties.forced) { + // if (!this.subtitleGroups_.groups[mediaGroup].forced[properties.language]) { + // this.subtitleGroups_[mediaGroup].forced[properties.language] = []; + // } + + // this.subtitleGroups_[mediaGroup].forced[properties.language].push({ + // id: label, + // properties + // }); + // } else { + // this.subtitleGroups_.groups[mediaGroup].unforced.push({ + // id: label, + // properties + // }); + + // if (typeof this.subtitleGroups_.tracks[label] === 'undefined') { + // let track = this.tech_.addRemoteTextTrack({ + // id: label, + // kind: 'subtitles', + // enabled: false, + // language: properties.language, + // label + // }, true).track; + + // track.properties_ = properties; + // this.subtitleGroups_.tracks[label] = track; + // } + // } + if (!properties.forced) { + this.subtitleGroups_.groups[mediaGroup].push( + videojs.mergeOptions({ id: label }, properties)); + + if (typeof this.subtitleGroups_.tracks[label] === 'undefined') { + let track = this.tech_.addRemoteTextTrack({ + id: label, + kind: 'subtitles', + enabled: false, + language: properties.language, + label + }, true).track; + + this.subtitleGroups_.tracks[label] = track; + } + } + } + } + + // for (let mediaGroup in this.subtitleGroups_) { + // for (let lang in this.subtitleGroups_[mediaGroup].forced) { + // let foundLang = this.subtitleGroups_[mediaGroup].tracks.find((track) => { + // return track.language === lang; + // }); + + // if (typeof foundLang === 'undefined') { + // let forcedLang = this.subtitleGroups_[mediaGroup].forced[lang].shift(); + + // let track = this.tech_.addRemoteTextTrack({ + // id: forcedLang.id, + // kind: 'subtitles', + // enabled: false, + // language: lang, + // label: forcedLang.id + // }, true).track; + + // track.properties_ = forcedLang.properties; + // this.subtitleGroups_[mediaGroup].tracks.push(track); + // } + // } + // } + + // Do not enable a default subtitle track. Wait for user interaction instead. + + // enable the default active track + // let activeName = (this.activeSubtitleGroup().unforced.filter((subtitleTrack) => { + // return subtitleTrack.properties.default; + // })[0] || this.activeSubtitleGroup().unforced[0]).id; + } /** * Call load on our SegmentLoaders @@ -602,6 +711,21 @@ export class MasterPlaylistController extends videojs.EventTarget { return result || this.audioGroups_.main; } + /** + * Returns the subtitle group for the currently active primary + * media playlist. + */ + activeSubtitleGroup() { + let videoPlaylist = this.masterPlaylistLoader_.media(); + let result; + + if (videoPlaylist.attributes && videoPlaylist.attributes.SUBTITLES) { + result = this.subtitleGroups_.groups[videoPlaylist.attributes.SUBTITLES]; + } + + return result || this.subtitleGroups_.groups.main; + } + /** * Determine the correct audio rendition based on the active * AudioTrack and initialize a PlaylistLoader and SegmentLoader if @@ -642,7 +766,7 @@ export class MasterPlaylistController extends videojs.EventTarget { this.audioPlaylistLoader_ = new PlaylistLoader(track.properties_.resolvedUri, this.hls_, this.withCredentials); - this.audioPlaylistLoader_.start(); + this.audioPlaylistLoader_.load(); this.audioPlaylistLoader_.on('loadedmetadata', () => { let audioPlaylist = this.audioPlaylistLoader_.media(); @@ -686,6 +810,97 @@ export class MasterPlaylistController extends videojs.EventTarget { }); } + /** + * Determine the correct subtitle playlist based on the active + * SubtitleTrack and initialize a PlaylistLoader and SegmentLoader if + * necessary. This method is called once automatically before + * playback begins to enable the default subtitle track and should be + * invoked again if the track is changed. + */ + setupSubtitles() { + // determine whether seperate loaders are required for the audio + // rendition + let subtitleGroup = this.activeSubtitleGroup(); + let track; + for (let trackName in this.subtitleGroups_.tracks) { + if (this.subtitleGroups_.tracks[trackName].mode === 'showing') { + track = this.subtitleGroups_.tracks[trackName]; + break; + } + } + + if (!track) { + // stop playlist and segment loading for subtitles + if (this.subtitlePlaylistLoader_) { + this.subtitlePlaylistLoader_.pause(); + } + return; + } + + let properties = subtitleGroup.filter((subtitleProperties) => { + return subtitleProperties.id === track.id; + })[0]; + + // this.subtitleSegmentLoader_.resetEverything(); + + // startup playlist and segment loaders for the enabled subtitle track + if (!this.subtitlePlaylistLoader_ || this.subtitlePlaylistLoader_.state === 'HAVE_NOTHING') { + if (this.subtitlePlaylistLoader_) { + this.subtitlePlaylistLoader_.dispose(); + } + + this.subtitlePlaylistLoader_ = new PlaylistLoader(properties.resolvedUri, + this.hls_, + this.withCredentials); + + this.subtitlePlaylistLoader_.on('loadedmetadata', () => { + let subtitlePlaylist = this.subtitlePlaylistLoader_.media(); + + // this.subtitleSegmentLoader_.playlist(subtitlePlaylist, this.requestOptions_); + + // if the video is already playing, or if this isn't a live video and preload + // permits, start downloading segments + if (!this.tech_.paused() || + (subtitlePlaylist.endList && this.tech_.preload() !== 'none')) { + // this.subtitleSegmentLoader_.load(); + } + + if (!subtitlePlaylist.endList) { + this.subtitlePlaylistLoader_.trigger('firstplay'); + } + }); + + this.subtitlePlaylistLoader_.on('loadedplaylist', () => { + let updatedPlaylist; + + if (this.subtitlePlaylistLoader_) { + updatedPlaylist = this.subtitlePlaylistLoader_.media(); + } + + if (!updatedPlaylist) { + return; + } + + // this.subtitleSegmentLoader_.playlist(updatedPlaylist, this.requestOptions_); + }); + + this.subtitlePlaylistLoader_.on('error', () => { + videojs.log.warn('Problem encountered loading the alternate audio track' + + '. Switching back to default.'); + this.subtitlePlaylistLoader_.abort(); + this.setupSubtiles(); + }); + } + + let media = this.subtitlePlaylistLoader_.media(); + + if (media && properties.resolvedUri !== media.resolvedUri) { + this.subtitlePlaylistLoader_.media(properties.resolvedUri); + } + + this.subtitlePlaylistLoader_.load(); + } + /** * Re-tune playback quality level for the current player * conditions. This method may perform destructive actions, like diff --git a/src/playlist-loader.js b/src/playlist-loader.js index dad1b40e9..2b075207b 100644 --- a/src/playlist-loader.js +++ b/src/playlist-loader.js @@ -416,20 +416,32 @@ const PlaylistLoader = function(srcUrl, hls, withCredentials) { loader.pause = () => { loader.stopRequest(); window.clearTimeout(mediaUpdateTimeout); + if (loader.state === 'HAVE_NOTHING') { + // If we pause the loader before any data has been retrieved, its as if we never + // started, so reset to an unstarted state. + loader.started = false; + } }; /** * start loading of the playlist */ loader.load = () => { - if (loader.started) { - if (!loader.media().endList) { - loader.trigger('mediaupdatetimeout'); - } else { - loader.trigger('loadedplaylist'); - } - } else { + if (!loader.started) { loader.start(); + return; + } + + let media = loader.media(); + + if (!media) { + return; + } + + if (!media.endList) { + loader.trigger('mediaupdatetimeout'); + } else { + loader.trigger('loadedplaylist'); } }; @@ -488,16 +500,18 @@ const PlaylistLoader = function(srcUrl, hls, withCredentials) { } // resolve any media group URIs - for (let groupKey in loader.master.mediaGroups.AUDIO) { - for (let labelKey in loader.master.mediaGroups.AUDIO[groupKey]) { - let alternateAudio = loader.master.mediaGroups.AUDIO[groupKey][labelKey]; - - if (alternateAudio.uri) { - alternateAudio.resolvedUri = - resolveUrl(loader.master.uri, alternateAudio.uri); + ['AUDIO', 'SUBTITLES'].forEach((mediaType) => { + for (let groupKey in loader.master.mediaGroups[mediaType]) { + for (let labelKey in loader.master.mediaGroups[mediaType][groupKey]) { + let mediaProperties = loader.master.mediaGroups[mediaType][groupKey][labelKey]; + + if (mediaProperties.uri) { + mediaProperties.resolvedUri = + resolveUrl(loader.master.uri, mediaProperties.uri); + } } } - } + }); loader.trigger('loadedplaylist'); if (!request) { diff --git a/src/videojs-contrib-hls.js b/src/videojs-contrib-hls.js index 3fa79f064..69c08a3d0 100644 --- a/src/videojs-contrib-hls.js +++ b/src/videojs-contrib-hls.js @@ -377,6 +377,10 @@ class HlsHandler extends Component { this.masterPlaylistController_.setupAudio(); }; + this.textTrackChange_ = () => { + this.masterPlaylistController_.setupSubtitles(); + } + this.on(this.tech_, 'play', this.play); } @@ -537,6 +541,7 @@ class HlsHandler extends Component { this.masterPlaylistController_.on('sourceopen', () => { this.tech_.audioTracks().addEventListener('change', this.audioTrackChange_); + this.tech_.remoteTextTracks().addEventListener('change', this.textTrackChange_); }); this.masterPlaylistController_.on('selectedinitialmedia', () => { @@ -649,6 +654,7 @@ class HlsHandler extends Component { this.qualityLevels_.dispose(); } this.tech_.audioTracks().removeEventListener('change', this.audioTrackChange_); + this.tech_.remoteTextTracks().removeEventListener('change', this.textTrackChange_); super.dispose(); } } From 29d74876cea0571c891094d16ffb5b909bd35f20 Mon Sep 17 00:00:00 2001 From: Matthew Neil Date: Thu, 23 Feb 2017 19:06:55 -0500 Subject: [PATCH 02/50] start of vtt-segment-loader, request segment and parse cues --- src/master-playlist-controller.js | 23 ++++- src/vtt-segment-loader.js | 156 +++++++++++++++++++----------- utils/stats/index.html | 4 +- 3 files changed, 122 insertions(+), 61 deletions(-) diff --git a/src/master-playlist-controller.js b/src/master-playlist-controller.js index 93892c7e9..835fdaaa7 100644 --- a/src/master-playlist-controller.js +++ b/src/master-playlist-controller.js @@ -3,6 +3,7 @@ */ import PlaylistLoader from './playlist-loader'; import SegmentLoader from './segment-loader'; +import VTTSegmentLoader from './vtt-segment-loader'; import Ranges from './ranges'; import videojs from 'video.js'; import AdCueTags from './ad-cue-tags'; @@ -288,12 +289,17 @@ export class MasterPlaylistController extends videojs.EventTarget { loaderType: 'audio' })); + this.subtitleSegmentLoader_ = new VTTSegmentLoader(videojs.mergeOptions(segmentLoaderOptions, { + loaderType: 'vtt' + })); + this.setupSegmentLoaderListeners_(); // Create SegmentLoader stat-getters loaderStats.forEach((stat) => { this[stat + '_'] = sumLoaderStat.bind(this, stat); }); + this.masterPlaylistLoader_.load(); } @@ -469,6 +475,13 @@ export class MasterPlaylistController extends videojs.EventTarget { this.audioPlaylistLoader_ = null; this.setupAudio(); }); + + this.subtitleSegmentLoader_.on('error', () => { + videojs.log.warn('Problem encountered with the current subtitle track. Switching' + + ' back to default.'); + this.subtitleSegmentLoader_.abort(); + this.setupSubtitles(); + }) } handleAudioinfoUpdate_(event) { @@ -841,7 +854,7 @@ export class MasterPlaylistController extends videojs.EventTarget { return subtitleProperties.id === track.id; })[0]; - // this.subtitleSegmentLoader_.resetEverything(); + this.subtitleSegmentLoader_.resetEverything(); // startup playlist and segment loaders for the enabled subtitle track if (!this.subtitlePlaylistLoader_ || this.subtitlePlaylistLoader_.state === 'HAVE_NOTHING') { @@ -856,13 +869,13 @@ export class MasterPlaylistController extends videojs.EventTarget { this.subtitlePlaylistLoader_.on('loadedmetadata', () => { let subtitlePlaylist = this.subtitlePlaylistLoader_.media(); - // this.subtitleSegmentLoader_.playlist(subtitlePlaylist, this.requestOptions_); + this.subtitleSegmentLoader_.playlist(subtitlePlaylist, this.requestOptions_); // if the video is already playing, or if this isn't a live video and preload // permits, start downloading segments if (!this.tech_.paused() || (subtitlePlaylist.endList && this.tech_.preload() !== 'none')) { - // this.subtitleSegmentLoader_.load(); + this.subtitleSegmentLoader_.load(); } if (!subtitlePlaylist.endList) { @@ -881,11 +894,11 @@ export class MasterPlaylistController extends videojs.EventTarget { return; } - // this.subtitleSegmentLoader_.playlist(updatedPlaylist, this.requestOptions_); + this.subtitleSegmentLoader_.playlist(updatedPlaylist, this.requestOptions_); }); this.subtitlePlaylistLoader_.on('error', () => { - videojs.log.warn('Problem encountered loading the alternate audio track' + + videojs.log.warn('Problem encountered loading the subtitle track' + '. Switching back to default.'); this.subtitlePlaylistLoader_.abort(); this.setupSubtiles(); diff --git a/src/vtt-segment-loader.js b/src/vtt-segment-loader.js index 62e4beac4..1eefa4084 100644 --- a/src/vtt-segment-loader.js +++ b/src/vtt-segment-loader.js @@ -1,5 +1,5 @@ /** - * @file segment-loader.js + * @file vtt-segment-loader.js */ import {getMediaInfoForTime_ as getMediaInfoForTime} from './playlist'; import videojs from 'video.js'; @@ -59,11 +59,11 @@ const initSegmentId = function(initSegment) { /** * An object that manages segment loading and appending. * - * @class SegmentLoader + * @class VTTSegmentLoader * @param {Object} options required and optional options * @extends videojs.EventTarget */ -export default class SegmentLoader extends videojs.EventTarget { +export default class VTTSegmentLoader extends videojs.EventTarget { constructor(options) { super(); // check pre-conditions @@ -95,7 +95,6 @@ export default class SegmentLoader extends videojs.EventTarget { this.mediaSource_ = settings.mediaSource; this.hls_ = settings.hls; this.loaderType_ = settings.loaderType; - this.segmentMetadataTrack_ = settings.segmentMetadataTrack; // private instance variables this.checkBufferTimeout_ = null; @@ -105,6 +104,7 @@ export default class SegmentLoader extends videojs.EventTarget { this.mimeType_ = null; this.sourceUpdater_ = null; this.xhrOptions_ = null; + this.timestampOffset_ = 0; // Fragmented mp4 playback this.activeInitSegmentId_ = null; @@ -209,6 +209,19 @@ export default class SegmentLoader extends videojs.EventTarget { return this.error_; } + /** + * Indicates which time ranges are buffered + */ + buffered() { + // TODO + return videojs.createTimeRanges(); + } + + timestampOffset() { + // TODO + return this.timestampOffset_; + } + /** * load a playlist and start to fill the buffer */ @@ -226,15 +239,14 @@ export default class SegmentLoader extends videojs.EventTarget { this.syncController_.setDateTimeMapping(this.playlist_); // if all the configuration is ready, initialize and begin loading - if (this.state === 'INIT' && this.mimeType_) { + if (this.state === 'INIT') { return this.init_(); } // if we're in the middle of processing a segment already, don't // kick off an additional segment request - if (!this.sourceUpdater_ || - (this.state !== 'READY' && - this.state !== 'INIT')) { + if (this.state !== 'READY' && + this.state !== 'INIT') { return; } @@ -250,7 +262,6 @@ export default class SegmentLoader extends videojs.EventTarget { */ init_() { this.state = 'READY'; - this.sourceUpdater_ = new SourceUpdater(this.mediaSource_, this.mimeType_); this.resetEverything(); return this.monitorBuffer_(); } @@ -287,7 +298,7 @@ export default class SegmentLoader extends videojs.EventTarget { // if we were unpaused but waiting for a playlist, start // buffering now - if (this.mimeType_ && this.state === 'INIT' && !this.paused()) { + if (this.state === 'INIT' && !this.paused()) { return this.init_(); } @@ -355,27 +366,6 @@ export default class SegmentLoader extends videojs.EventTarget { return this.checkBufferTimeout_ === null; } - /** - * create/set the following mimetype on the SourceBuffer through a - * SourceUpdater - * - * @param {String} mimeType the mime type string to use - */ - mimeType(mimeType) { - if (this.mimeType_) { - return; - } - - this.mimeType_ = mimeType; - // if we were unpaused but waiting for a sourceUpdater, start - // buffering now - if (this.playlist_ && - this.state === 'INIT' && - !this.paused()) { - this.init_(); - } - } - /** * Delete all the buffered data and reset the SegmentLoader */ @@ -413,7 +403,6 @@ export default class SegmentLoader extends videojs.EventTarget { if (this.sourceUpdater_) { this.sourceUpdater_.remove(start, end); } - removeCuesFromTrack(start, end, this.segmentMetadataTrack_); } /** @@ -458,10 +447,6 @@ export default class SegmentLoader extends videojs.EventTarget { * @private */ fillBuffer_() { - if (this.sourceUpdater_.updating()) { - return; - } - if (!this.syncPoint_) { this.syncPoint_ = this.syncController_.getSyncPoint(this.playlist_, this.mediaSource_.duration, @@ -470,7 +455,7 @@ export default class SegmentLoader extends videojs.EventTarget { } // see if we need to begin loading immediately - let segmentInfo = this.checkBuffer_(this.sourceUpdater_.buffered(), + let segmentInfo = this.checkBuffer_(this.buffered(), this.playlist_, this.mediaIndex, this.hasPlayed_(), @@ -481,15 +466,6 @@ export default class SegmentLoader extends videojs.EventTarget { return; } - let isEndOfStream = detectEndOfStream(this.playlist_, - this.mediaSource_, - segmentInfo.mediaIndex); - - if (isEndOfStream) { - this.mediaSource_.endOfStream(); - return; - } - if (segmentInfo.mediaIndex === this.playlist_.segments.length - 1 && this.mediaSource_.readyState === 'ended' && !this.seeking_()) { @@ -504,8 +480,7 @@ export default class SegmentLoader extends videojs.EventTarget { // the currently set timestampOffset if (segmentInfo.timeline !== this.currentTimeline_ || ((segmentInfo.startOfSegment !== null) && - segmentInfo.startOfSegment < this.sourceUpdater_.timestampOffset())) { - this.syncController_.reset(); + segmentInfo.startOfSegment < this.timestampOffset())) { segmentInfo.timestampOffset = segmentInfo.startOfSegment; } @@ -723,6 +698,7 @@ export default class SegmentLoader extends videojs.EventTarget { if (seekable.length && seekable.start(0) > 0 && seekable.start(0) < currentTime) { +<<<<<<< HEAD removeToTime = seekable.start(0); } else { removeToTime = currentTime - 60; @@ -872,9 +848,51 @@ export default class SegmentLoader extends videojs.EventTarget { } segmentInfo.endOfAllRequests = simpleSegment.endOfAllRequests; + + let segment = segmentInfo.segment; + + segment.requested = true; + + // Make sure that vttjs has loaded, otherwise, wait till it finished loading + if (typeof window.WebVTT !== 'function') { + const loadHandler = () => { + this.parseVTTCues_(segmentInfo, this.subtitleTrack_); + this.handleSegment_(); + }; + + this.tech_.on('vttjsloaded', loadHandler); + this.tech_.on('vttjserror', () => { + this.tech_.off('vttjsloaded', loadHandler); + }); + + return; + } + + this.parseVTTCues_(segmentInfo, this.subtitleTrack_); this.handleSegment_(); } + parseVTTCues_(segmentInfo, track) { + const parser = new window.WebVTT.Parser(window, + window.vttjs, + window.WebVTT.StringDecoder()); + const errors = []; + const cues = []; + let timestampmap = { MPEGTS: 0, LOCAL: 0 }; + + parser.oncue = cues.push; + parser.onparsingerror = errors.push; + parser.ontimestampmap = (map) => timestampmap = map; + + parser.onflush = () => { + segmentInfo.cues = cues; + segmentInfo.timestampMap = timestampmap; + } + + parser.parse(segmentInfo.bytes); + parser.flush(); + } + /** * append a decrypted segement to the SourceBuffer through a SourceUpdater * @@ -888,10 +906,10 @@ export default class SegmentLoader extends videojs.EventTarget { this.state = 'APPENDING'; - const segmentInfo = this.pendingSegment_; - const segment = segmentInfo.segment; + let segmentInfo = this.pendingSegment_; + let segment = segmentInfo.segment; - this.syncController_.probeSegmentInfo(segmentInfo); + this.updateTimeMapping_(segmentInfo); if (segmentInfo.isSyncRequest) { this.trigger('syncinfoupdate'); @@ -901,8 +919,8 @@ export default class SegmentLoader extends videojs.EventTarget { } if (segmentInfo.timestampOffset !== null && - segmentInfo.timestampOffset !== this.sourceUpdater_.timestampOffset()) { - this.sourceUpdater_.timestampOffset(segmentInfo.timestampOffset); + segmentInfo.timestampOffset !== this.timestampOffset()) { + this.timestampOffset_ = segmentInfo.timestampOffset; } // if the media initialization segment is changing, append it @@ -931,6 +949,37 @@ export default class SegmentLoader extends videojs.EventTarget { this.handleUpdateEnd_.bind(this)); } + updateTimeMapping_(segmentInfo) { + let segment = segmentInfo.segment; + + let mappingObj = this.syncController_.timelines[segmentInfo.timeline]; + let timestampMap = segmentInfo.timestampMap; + + if (!mappingObj || !segmentInfo.cues.length) { + // If the sync controller does not have a mapping of TS to Media Time for the + // timeline, then we don't have enough information to update the segment and cue + // start/end times + // If there are no cues, we also do not have enough information to figure out + // segment timing + return; + } + + const diff = (timestampMap.MPEGTS / 90000) - timestampMap.LOCAL + mappingObj.mapping; + + segmentInfo.cues.forEach((cue) => { + // First convert cue time to TS time using the timestamp-map provided within the vtt + cue.startTime += diff; + cue.endTime += diff; + }); + + const firstStart = segmentInfo.cues[0].startTime; + const lastStart = segmentInfo.cues[segmentInfo.cues.length - 1].startTime; + const midPoint = (firstStart + lastStart) / 2; + + segment.start = midPoint - (segment.duration / 2); + segment.end = midPoint + (segment.duration / 2); + } + /** * callback to run when appendBuffer is finished. detects if we are * in a good state to do things with the data we got, or if we need @@ -955,7 +1004,6 @@ export default class SegmentLoader extends videojs.EventTarget { this.pendingSegment_ = null; this.recordThroughput_(segmentInfo); - this.addSegmentMetadataCue_(segmentInfo); this.state = 'READY'; diff --git a/utils/stats/index.html b/utils/stats/index.html index edd208ddd..dbf103a75 100644 --- a/utils/stats/index.html +++ b/utils/stats/index.html @@ -7,7 +7,7 @@ - + @@ -56,7 +56,7 @@