Skip to content
This repository has been archived by the owner on Jan 12, 2019. It is now read-only.

Commit

Permalink
Merge remote-tracking branch 'origin/vtt-loader' into feature/webvtt
Browse files Browse the repository at this point in the history
  • Loading branch information
mjneil committed Mar 14, 2017
2 parents 9266df0 + 7f19796 commit 1610a1f
Show file tree
Hide file tree
Showing 12 changed files with 1,429 additions and 489 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ are some highlights:
- AES-128 segment encryption
- CEA-608 captions are automatically translated into standard HTML5
[caption text tracks][0]
- In-Manifest WebVTT subtitles are automatically translated into standard HTML5
subtitle tracks
- Timed ID3 Metadata is automatically translated into HTML5 metedata
text tracks
- Highly customizable adaptive bitrate selection
Expand Down
204 changes: 201 additions & 3 deletions src/master-playlist-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -216,13 +217,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;
Expand Down Expand Up @@ -259,6 +260,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
Expand All @@ -272,17 +274,22 @@ export class MasterPlaylistController extends videojs.EventTarget {
loaderType: 'audio'
}));

segmentLoaderOptions.loaderType = 'vtt';
this.subtitleSegmentLoader_ = new VTTSegmentLoader(segmentLoaderOptions);

this.decrypter_.onmessage = (event) => {
if (event.data.source === 'main') {
this.mainSegmentLoader_.handleDecrypted_(event.data);
} else if (event.data.source === 'audio') {
this.audioSegmentLoader_.handleDecrypted_(event.data);
} else if (event.data.source === 'vtt') {
this.subtitleSegmentLoader_.handleDecrypted_(event.data);
}
};

this.setupSegmentLoaderListeners_();

this.masterPlaylistLoader_.start();
this.masterPlaylistLoader_.load();
}

/**
Expand Down Expand Up @@ -314,6 +321,9 @@ export class MasterPlaylistController extends videojs.EventTarget {
this.fillAudioTracks_();
this.setupAudio();

this.fillSubtitleTracks_();
this.setupSubtitles();

try {
this.setupSourceBuffers_();
} catch (e) {
Expand Down Expand Up @@ -410,6 +420,7 @@ export class MasterPlaylistController extends videojs.EventTarget {
this.setupAudio();
this.trigger('audioupdate');
}
this.setupSubtitles();

this.tech_.trigger({
type: 'mediachange',
Expand Down Expand Up @@ -452,6 +463,8 @@ export class MasterPlaylistController extends videojs.EventTarget {
this.audioPlaylistLoader_ = null;
this.setupAudio();
});

this.subtitleSegmentLoader_.on('error', this.handleSubtitleError_.bind(this));
}

handleAudioinfoUpdate_(event) {
Expand Down Expand Up @@ -598,6 +611,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] = { unforced: [], forced: {} };
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;
}
}
}
}
}

/**
* Call load on our SegmentLoaders
*/
Expand All @@ -606,6 +658,9 @@ export class MasterPlaylistController extends videojs.EventTarget {
if (this.audioPlaylistLoader_) {
this.audioSegmentLoader_.load();
}
if (this.subtitlePlaylistLoader_) {
this.subtitleSegmentLoader_.load();
}
}

/**
Expand All @@ -623,6 +678,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();
}

/**
* Determine the correct audio rendition based on the active
* AudioTrack and initialize a PlaylistLoader and SegmentLoader if
Expand Down Expand Up @@ -663,7 +762,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();
Expand Down Expand Up @@ -707,6 +806,91 @@ 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
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
Expand Down Expand Up @@ -868,6 +1052,9 @@ export class MasterPlaylistController extends videojs.EventTarget {
if (this.audioPlaylistLoader_) {
this.audioSegmentLoader_.pause();
}
if (this.subtitlePlaylistLoader_) {
this.subtitleSegmentLoader_.pause();
}
}

/**
Expand Down Expand Up @@ -909,12 +1096,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();
}
}
}

Expand Down Expand Up @@ -1032,7 +1226,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();
}

/**
Expand Down
Loading

0 comments on commit 1610a1f

Please sign in to comment.