This repository has been archived by the owner on Jan 12, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 793
In-Manifest WebVTT Support #1057
Merged
Merged
Changes from all commits
Commits
Show all changes
50 commits
Select commit
Hold shift + click to select a range
4220a0b
added vtt playlist loader
mjneil 29d7487
start of vtt-segment-loader, request segment and parse cues
mjneil 0a5fc1e
Add cues when handling subtitle segment and clear cues when changing …
gesinger 75bf022
Don't call endOfStream from vtt segment loader
gesinger 0c788ba
Move creation of new subtitles playlist loader on URI change
gesinger 06a6c65
Reset subtitle segment loader only when using new subtitles
gesinger 5fdf933
Fill out buffered function for vtt segment loader
gesinger 7b156bc
Wait for video timestamp offsets in vtt segment loader
gesinger 69b5e5e
Fix some loads and disposes for subtitle segment loader
gesinger 739ba90
Don't use a default subtitle track
gesinger 39741d0
Fix some subtitle playlist and segment loader pausing and clearing
gesinger e7103e3
Fill out vtt segment loader's remove function
gesinger 836c5b4
Fix cue time references in vtt buffered check
gesinger d2dc66c
fix delay when switching subtitle tracks
mjneil d91da5e
unpause subtitle segment loader when selecting active track
mjneil 840c709
Reset to subtitles off when subtitle playlist errors
gesinger 0d150f1
Remove unnecessary nullification of subtitle playlist loader on segme…
gesinger 9ba9bc7
Handle subtitle errors generically
gesinger 36a4099
Remove commented out subtitle code for handling forced subtitles
gesinger 3ecaa71
Move warn log to handleSubtitleError and dispose subtitle segment loa…
gesinger f3939fe
add decryption for vtt loader
mjneil 5483ea6
Fix linting errors
gesinger 21885c2
add support for vtt init segment
mjneil 50f88a8
append line terminators to init segment instead of segment
mjneil ea37686
require track before loading can begin, start tests
mjneil ffcc4bc
fix vtt loader tests
mjneil fa021d4
fix vtt loader waiting on timeline and vttjs
mjneil 1791581
test vtt segment time mapping
mjneil 5a5cfe5
add vttjs error handling
mjneil bb96ff8
Removed unused subtitleupdate event
gesinger 3907bc9
Add test for adding subtitle tracks when media playlist is loaded
gesinger 9941345
Add test for switching off subtitles on subtitle errors
gesinger ab2fbfc
Add test for pausing subtitle loader on tech error
gesinger 379bfc4
Add test for disposing subtitle segment and playlist loaders
gesinger 32dd51c
Add test for resetting subtitle segment loader on seeks
gesinger 9b78a9b
Add test for getting active subtitle group
gesinger 443be25
Add test for getting active subtitle track
gesinger 1afd470
Add test for handling subtitle errors from master playlist controller
gesinger 5102875
Add test for subtitles setup
gesinger 178fef2
update readme
mjneil 46ffa3e
remove forced logging from vtt-loader
mjneil f850fdf
Add master-subtitles.m3u8 manifest for tests
gesinger 47fcb24
ignore: copy segment-loader files for diffing
mjneil 85aba5e
merge fixes
mjneil f66f3f1
prevent infinitely requesting same segment when segment has no subtit…
mjneil 047083c
Fix MasterPlaylistController subtitle tests by considering segment-me…
gesinger 514b06b
ignore: fix missing semicolon
mjneil 4dbe477
ignore: rebase fix
mjneil fed4daa
put init segment getting/setting into function
mjneil ebba86d
remove path to vttjs
mjneil File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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'; | ||
|
@@ -231,13 +232,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 +275,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 | ||
|
@@ -287,14 +289,18 @@ export class MasterPlaylistController extends videojs.EventTarget { | |
loaderType: 'audio' | ||
})); | ||
|
||
this.setupSegmentLoaderListeners_(); | ||
this.subtitleSegmentLoader_ = new VTTSegmentLoader(videojs.mergeOptions(segmentLoaderOptions, { | ||
loaderType: 'vtt' | ||
})); | ||
|
||
this.masterPlaylistLoader_.start(); | ||
this.setupSegmentLoaderListeners_(); | ||
|
||
// Create SegmentLoader stat-getters | ||
loaderStats.forEach((stat) => { | ||
this[stat + '_'] = sumLoaderStat.bind(this, stat); | ||
}); | ||
|
||
this.masterPlaylistLoader_.load(); | ||
} | ||
|
||
/** | ||
|
@@ -326,6 +332,9 @@ export class MasterPlaylistController extends videojs.EventTarget { | |
this.fillAudioTracks_(); | ||
this.setupAudio(); | ||
|
||
this.fillSubtitleTracks_(); | ||
this.setupSubtitles(); | ||
|
||
try { | ||
this.setupSourceBuffers_(); | ||
} catch (e) { | ||
|
@@ -422,6 +431,7 @@ export class MasterPlaylistController extends videojs.EventTarget { | |
this.setupAudio(); | ||
this.trigger('audioupdate'); | ||
} | ||
this.setupSubtitles(); | ||
|
||
this.tech_.trigger({ | ||
type: 'mediachange', | ||
|
@@ -465,6 +475,8 @@ export class MasterPlaylistController extends videojs.EventTarget { | |
this.audioPlaylistLoader_ = null; | ||
this.setupAudio(); | ||
}); | ||
|
||
this.subtitleSegmentLoader_.on('error', this.handleSubtitleError_.bind(this)); | ||
} | ||
|
||
handleAudioinfoUpdate_(event) { | ||
|
@@ -576,6 +588,45 @@ 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 || {}; | ||
|
||
for (let mediaGroup in mediaGroups.SUBTITLES) { | ||
if (!this.subtitleGroups_.groups[mediaGroup]) { | ||
this.subtitleGroups_.groups[mediaGroup] = []; | ||
} | ||
|
||
for (let label in mediaGroups.SUBTITLES[mediaGroup]) { | ||
let properties = mediaGroups.SUBTITLES[mediaGroup][label]; | ||
|
||
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; | ||
} | ||
} | ||
} | ||
} | ||
|
||
// Do not enable a default subtitle track. Wait for user interaction instead. | ||
} | ||
|
||
/** | ||
* Call load on our SegmentLoaders | ||
|
@@ -585,6 +636,9 @@ export class MasterPlaylistController extends videojs.EventTarget { | |
if (this.audioPlaylistLoader_) { | ||
this.audioSegmentLoader_.load(); | ||
} | ||
if (this.subtitlePlaylistLoader_) { | ||
this.subtitleSegmentLoader_.load(); | ||
} | ||
} | ||
|
||
/** | ||
|
@@ -602,6 +656,50 @@ 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) { | ||
return null; | ||
} | ||
|
||
if (videoPlaylist.attributes && videoPlaylist.attributes.SUBTITLES) { | ||
result = this.subtitleGroups_.groups[videoPlaylist.attributes.SUBTITLES]; | ||
} | ||
|
||
return result || this.subtitleGroups_.groups.main; | ||
} | ||
|
||
activeSubtitleTrack_() { | ||
for (let trackName in this.subtitleGroups_.tracks) { | ||
if (this.subtitleGroups_.tracks[trackName].mode === 'showing') { | ||
return this.subtitleGroups_.tracks[trackName]; | ||
} | ||
} | ||
|
||
return null; | ||
} | ||
|
||
handleSubtitleError_() { | ||
videojs.log.warn('Problem encountered loading the subtitle track' + | ||
'. Switching back to default.'); | ||
|
||
this.subtitleSegmentLoader_.abort(); | ||
|
||
let track = this.activeSubtitleTrack_(); | ||
|
||
if (track) { | ||
track.mode = 'disabled'; | ||
} | ||
|
||
this.setupSubtitles(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It shouldn't since just above we set the current active tracks |
||
} | ||
|
||
/** | ||
* Determine the correct audio rendition based on the active | ||
* AudioTrack and initialize a PlaylistLoader and SegmentLoader if | ||
|
@@ -642,7 +740,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 +784,93 @@ 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() { | ||
let subtitleGroup = this.activeSubtitleGroup_(); | ||
let track = this.activeSubtitleTrack_(); | ||
|
||
this.subtitleSegmentLoader_.pause(); | ||
|
||
if (!track) { | ||
// stop playlist and segment loading for subtitles | ||
if (this.subtitlePlaylistLoader_) { | ||
this.subtitlePlaylistLoader_.dispose(); | ||
this.subtitlePlaylistLoader_ = null; | ||
} | ||
return; | ||
} | ||
|
||
let properties = subtitleGroup.filter((subtitleProperties) => { | ||
return subtitleProperties.id === track.id; | ||
})[0]; | ||
|
||
// startup playlist and segment loaders for the enabled subtitle track | ||
if (!this.subtitlePlaylistLoader_ || | ||
// if the media hasn't loaded yet, we don't have the URI to check, so it is | ||
// easiest to simply recreate the playlist loader | ||
!this.subtitlePlaylistLoader_.media() || | ||
this.subtitlePlaylistLoader_.media().resolvedUri !== properties.resolvedUri) { | ||
|
||
if (this.subtitlePlaylistLoader_) { | ||
this.subtitlePlaylistLoader_.dispose(); | ||
} | ||
|
||
// reset the segment loader only when the subtitle playlist is changed instead of | ||
// every time setupSubtitles is called since switching subtitle tracks fires | ||
// multiple `change` events on the TextTrackList | ||
this.subtitleSegmentLoader_.resetEverything(); | ||
|
||
// can't reuse playlistloader because we're only using single renditions and not a | ||
// proper master | ||
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_); | ||
this.subtitleSegmentLoader_.track(this.activeSubtitleTrack_()); | ||
|
||
// 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.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', this.handleSubtitleError_.bind(this)); | ||
} | ||
|
||
if (this.subtitlePlaylistLoader_.media() && | ||
this.subtitlePlaylistLoader_.media().resolvedUri === properties.resolvedUri) { | ||
this.subtitleSegmentLoader_.load(); | ||
} else { | ||
this.subtitlePlaylistLoader_.load(); | ||
} | ||
} | ||
|
||
/** | ||
* Re-tune playback quality level for the current player | ||
* conditions. This method may perform destructive actions, like | ||
|
@@ -847,6 +1032,9 @@ export class MasterPlaylistController extends videojs.EventTarget { | |
if (this.audioPlaylistLoader_) { | ||
this.audioSegmentLoader_.pause(); | ||
} | ||
if (this.subtitlePlaylistLoader_) { | ||
this.subtitleSegmentLoader_.pause(); | ||
} | ||
} | ||
|
||
/** | ||
|
@@ -888,12 +1076,19 @@ export class MasterPlaylistController extends videojs.EventTarget { | |
this.audioSegmentLoader_.resetEverything(); | ||
this.audioSegmentLoader_.abort(); | ||
} | ||
if (this.subtitlePlaylistLoader_) { | ||
this.subtitleSegmentLoader_.resetEverything(); | ||
this.subtitleSegmentLoader_.abort(); | ||
} | ||
|
||
if (!this.tech_.paused()) { | ||
this.mainSegmentLoader_.load(); | ||
if (this.audioPlaylistLoader_) { | ||
this.audioSegmentLoader_.load(); | ||
} | ||
if (this.subtitlePlaylistLoader_) { | ||
this.subtitleSegmentLoader_.load(); | ||
} | ||
} | ||
} | ||
|
||
|
@@ -1011,7 +1206,11 @@ export class MasterPlaylistController extends videojs.EventTarget { | |
if (this.audioPlaylistLoader_) { | ||
this.audioPlaylistLoader_.dispose(); | ||
} | ||
if (this.subtitlePlaylistLoader_) { | ||
this.subtitlePlaylistLoader_.dispose(); | ||
} | ||
this.audioSegmentLoader_.dispose(); | ||
this.subtitleSegmentLoader_.dispose(); | ||
} | ||
|
||
/** | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
from #1043
@imbcmdth: At some point I feel like it might just be nice to have an array of active segment loaders. Then we can just do
this.segmentLoaders_.forEach((l) => l.load());
... not necessary now but maybe a good idea to think about soon.