605 lines
22 KiB
JavaScript
605 lines
22 KiB
JavaScript
/**
|
|
* @file sync-controller.js
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
Object.defineProperty(exports, '__esModule', {
|
|
value: true
|
|
});
|
|
|
|
var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
|
|
|
|
var _get = function get(_x2, _x3, _x4) { var _again = true; _function: while (_again) { var object = _x2, property = _x3, receiver = _x4; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x2 = parent; _x3 = property; _x4 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
|
|
|
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
|
|
|
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
|
|
|
|
function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
|
|
|
|
var _muxJsLibMp4Probe = require('mux.js/lib/mp4/probe');
|
|
|
|
var _muxJsLibMp4Probe2 = _interopRequireDefault(_muxJsLibMp4Probe);
|
|
|
|
var _muxJsLibToolsTsInspectorJs = require('mux.js/lib/tools/ts-inspector.js');
|
|
|
|
var _playlist = require('./playlist');
|
|
|
|
var _videoJs = require('video.js');
|
|
|
|
var _videoJs2 = _interopRequireDefault(_videoJs);
|
|
|
|
var syncPointStrategies = [
|
|
// Stategy "VOD": Handle the VOD-case where the sync-point is *always*
|
|
// the equivalence display-time 0 === segment-index 0
|
|
{
|
|
name: 'VOD',
|
|
run: function run(syncController, playlist, duration, currentTimeline, currentTime) {
|
|
if (duration !== Infinity) {
|
|
var syncPoint = {
|
|
time: 0,
|
|
segmentIndex: 0
|
|
};
|
|
|
|
return syncPoint;
|
|
}
|
|
return null;
|
|
}
|
|
},
|
|
// Stategy "ProgramDateTime": We have a program-date-time tag in this playlist
|
|
{
|
|
name: 'ProgramDateTime',
|
|
run: function run(syncController, playlist, duration, currentTimeline, currentTime) {
|
|
if (syncController.datetimeToDisplayTime && playlist.dateTimeObject) {
|
|
var playlistTime = playlist.dateTimeObject.getTime() / 1000;
|
|
var playlistStart = playlistTime + syncController.datetimeToDisplayTime;
|
|
var syncPoint = {
|
|
time: playlistStart,
|
|
segmentIndex: 0
|
|
};
|
|
|
|
return syncPoint;
|
|
}
|
|
return null;
|
|
}
|
|
},
|
|
// Stategy "Segment": We have a known time mapping for a timeline and a
|
|
// segment in the current timeline with timing data
|
|
{
|
|
name: 'Segment',
|
|
run: function run(syncController, playlist, duration, currentTimeline, currentTime) {
|
|
var segments = playlist.segments || [];
|
|
var syncPoint = null;
|
|
var lastDistance = null;
|
|
|
|
currentTime = currentTime || 0;
|
|
|
|
for (var i = 0; i < segments.length; i++) {
|
|
var segment = segments[i];
|
|
|
|
if (segment.timeline === currentTimeline && typeof segment.start !== 'undefined') {
|
|
var distance = Math.abs(currentTime - segment.start);
|
|
|
|
// Once the distance begins to increase, we have passed
|
|
// currentTime and can stop looking for better candidates
|
|
if (lastDistance !== null && lastDistance < distance) {
|
|
break;
|
|
}
|
|
|
|
if (!syncPoint || lastDistance === null || lastDistance >= distance) {
|
|
lastDistance = distance;
|
|
syncPoint = {
|
|
time: segment.start,
|
|
segmentIndex: i
|
|
};
|
|
}
|
|
}
|
|
}
|
|
return syncPoint;
|
|
}
|
|
},
|
|
// Stategy "Discontinuity": We have a discontinuity with a known
|
|
// display-time
|
|
{
|
|
name: 'Discontinuity',
|
|
run: function run(syncController, playlist, duration, currentTimeline, currentTime) {
|
|
var syncPoint = null;
|
|
|
|
currentTime = currentTime || 0;
|
|
|
|
if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) {
|
|
var lastDistance = null;
|
|
|
|
for (var i = 0; i < playlist.discontinuityStarts.length; i++) {
|
|
var segmentIndex = playlist.discontinuityStarts[i];
|
|
var discontinuity = playlist.discontinuitySequence + i + 1;
|
|
var discontinuitySync = syncController.discontinuities[discontinuity];
|
|
|
|
if (discontinuitySync) {
|
|
var distance = Math.abs(currentTime - discontinuitySync.time);
|
|
|
|
// Once the distance begins to increase, we have passed
|
|
// currentTime and can stop looking for better candidates
|
|
if (lastDistance !== null && lastDistance < distance) {
|
|
break;
|
|
}
|
|
|
|
if (!syncPoint || lastDistance === null || lastDistance >= distance) {
|
|
lastDistance = distance;
|
|
syncPoint = {
|
|
time: discontinuitySync.time,
|
|
segmentIndex: segmentIndex
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return syncPoint;
|
|
}
|
|
},
|
|
// Stategy "Playlist": We have a playlist with a known mapping of
|
|
// segment index to display time
|
|
{
|
|
name: 'Playlist',
|
|
run: function run(syncController, playlist, duration, currentTimeline, currentTime) {
|
|
if (playlist.syncInfo) {
|
|
var syncPoint = {
|
|
time: playlist.syncInfo.time,
|
|
segmentIndex: playlist.syncInfo.mediaSequence - playlist.mediaSequence
|
|
};
|
|
|
|
return syncPoint;
|
|
}
|
|
return null;
|
|
}
|
|
}];
|
|
|
|
exports.syncPointStrategies = syncPointStrategies;
|
|
|
|
var SyncController = (function (_videojs$EventTarget) {
|
|
_inherits(SyncController, _videojs$EventTarget);
|
|
|
|
function SyncController() {
|
|
var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
|
|
|
|
_classCallCheck(this, SyncController);
|
|
|
|
_get(Object.getPrototypeOf(SyncController.prototype), 'constructor', this).call(this);
|
|
// Segment Loader state variables...
|
|
// ...for synching across variants
|
|
this.inspectCache_ = undefined;
|
|
|
|
// ...for synching across variants
|
|
this.timelines = [];
|
|
this.discontinuities = [];
|
|
this.datetimeToDisplayTime = null;
|
|
|
|
if (options.debug) {
|
|
this.logger_ = _videoJs2['default'].log.bind(_videoJs2['default'], 'sync-controller ->');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Find a sync-point for the playlist specified
|
|
*
|
|
* A sync-point is defined as a known mapping from display-time to
|
|
* a segment-index in the current playlist.
|
|
*
|
|
* @param {Playlist} playlist
|
|
* The playlist that needs a sync-point
|
|
* @param {Number} duration
|
|
* Duration of the MediaSource (Infinite if playing a live source)
|
|
* @param {Number} currentTimeline
|
|
* The last timeline from which a segment was loaded
|
|
* @returns {Object}
|
|
* A sync-point object
|
|
*/
|
|
|
|
_createClass(SyncController, [{
|
|
key: 'getSyncPoint',
|
|
value: function getSyncPoint(playlist, duration, currentTimeline, currentTime) {
|
|
var syncPoints = this.runStrategies_(playlist, duration, currentTimeline, currentTime);
|
|
|
|
if (!syncPoints.length) {
|
|
// Signal that we need to attempt to get a sync-point manually
|
|
// by fetching a segment in the playlist and constructing
|
|
// a sync-point from that information
|
|
return null;
|
|
}
|
|
|
|
// Now find the sync-point that is closest to the currentTime because
|
|
// that should result in the most accurate guess about which segment
|
|
// to fetch
|
|
return this.selectSyncPoint_(syncPoints, { key: 'time', value: currentTime });
|
|
}
|
|
|
|
/**
|
|
* Calculate the amount of time that has expired off the playlist during playback
|
|
*
|
|
* @param {Playlist} playlist
|
|
* Playlist object to calculate expired from
|
|
* @param {Number} duration
|
|
* Duration of the MediaSource (Infinity if playling a live source)
|
|
* @returns {Number|null}
|
|
* The amount of time that has expired off the playlist during playback. Null
|
|
* if no sync-points for the playlist can be found.
|
|
*/
|
|
}, {
|
|
key: 'getExpiredTime',
|
|
value: function getExpiredTime(playlist, duration) {
|
|
if (!playlist || !playlist.segments) {
|
|
return null;
|
|
}
|
|
|
|
var syncPoints = this.runStrategies_(playlist, duration, playlist.discontinuitySequence, 0);
|
|
|
|
// Without sync-points, there is not enough information to determine the expired time
|
|
if (!syncPoints.length) {
|
|
return null;
|
|
}
|
|
|
|
var syncPoint = this.selectSyncPoint_(syncPoints, {
|
|
key: 'segmentIndex',
|
|
value: 0
|
|
});
|
|
|
|
// If the sync-point is beyond the start of the playlist, we want to subtract the
|
|
// duration from index 0 to syncPoint.segmentIndex instead of adding.
|
|
if (syncPoint.segmentIndex > 0) {
|
|
syncPoint.time *= -1;
|
|
}
|
|
|
|
return Math.abs(syncPoint.time + (0, _playlist.sumDurations)(playlist, syncPoint.segmentIndex, 0));
|
|
}
|
|
|
|
/**
|
|
* Runs each sync-point strategy and returns a list of sync-points returned by the
|
|
* strategies
|
|
*
|
|
* @private
|
|
* @param {Playlist} playlist
|
|
* The playlist that needs a sync-point
|
|
* @param {Number} duration
|
|
* Duration of the MediaSource (Infinity if playing a live source)
|
|
* @param {Number} currentTimeline
|
|
* The last timeline from which a segment was loaded
|
|
* @returns {Array}
|
|
* A list of sync-point objects
|
|
*/
|
|
}, {
|
|
key: 'runStrategies_',
|
|
value: function runStrategies_(playlist, duration, currentTimeline, currentTime) {
|
|
var syncPoints = [];
|
|
|
|
// Try to find a sync-point in by utilizing various strategies...
|
|
for (var i = 0; i < syncPointStrategies.length; i++) {
|
|
var strategy = syncPointStrategies[i];
|
|
var syncPoint = strategy.run(this, playlist, duration, currentTimeline, currentTime);
|
|
|
|
if (syncPoint) {
|
|
syncPoint.strategy = strategy.name;
|
|
syncPoints.push({
|
|
strategy: strategy.name,
|
|
syncPoint: syncPoint
|
|
});
|
|
this.logger_('syncPoint found via <' + strategy.name + '>:', syncPoint);
|
|
}
|
|
}
|
|
|
|
return syncPoints;
|
|
}
|
|
|
|
/**
|
|
* Selects the sync-point nearest the specified target
|
|
*
|
|
* @private
|
|
* @param {Array} syncPoints
|
|
* List of sync-points to select from
|
|
* @param {Object} target
|
|
* Object specifying the property and value we are targeting
|
|
* @param {String} target.key
|
|
* Specifies the property to target. Must be either 'time' or 'segmentIndex'
|
|
* @param {Number} target.value
|
|
* The value to target for the specified key.
|
|
* @returns {Object}
|
|
* The sync-point nearest the target
|
|
*/
|
|
}, {
|
|
key: 'selectSyncPoint_',
|
|
value: function selectSyncPoint_(syncPoints, target) {
|
|
var bestSyncPoint = syncPoints[0].syncPoint;
|
|
var bestDistance = Math.abs(syncPoints[0].syncPoint[target.key] - target.value);
|
|
var bestStrategy = syncPoints[0].strategy;
|
|
|
|
for (var i = 1; i < syncPoints.length; i++) {
|
|
var newDistance = Math.abs(syncPoints[i].syncPoint[target.key] - target.value);
|
|
|
|
if (newDistance < bestDistance) {
|
|
bestDistance = newDistance;
|
|
bestSyncPoint = syncPoints[i].syncPoint;
|
|
bestStrategy = syncPoints[i].strategy;
|
|
}
|
|
}
|
|
|
|
this.logger_('syncPoint with strategy <' + bestStrategy + '> chosen: ', bestSyncPoint);
|
|
return bestSyncPoint;
|
|
}
|
|
|
|
/**
|
|
* Save any meta-data present on the segments when segments leave
|
|
* the live window to the playlist to allow for synchronization at the
|
|
* playlist level later.
|
|
*
|
|
* @param {Playlist} oldPlaylist - The previous active playlist
|
|
* @param {Playlist} newPlaylist - The updated and most current playlist
|
|
*/
|
|
}, {
|
|
key: 'saveExpiredSegmentInfo',
|
|
value: function saveExpiredSegmentInfo(oldPlaylist, newPlaylist) {
|
|
var mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence;
|
|
|
|
// When a segment expires from the playlist and it has a start time
|
|
// save that information as a possible sync-point reference in future
|
|
for (var i = mediaSequenceDiff - 1; i >= 0; i--) {
|
|
var lastRemovedSegment = oldPlaylist.segments[i];
|
|
|
|
if (lastRemovedSegment && typeof lastRemovedSegment.start !== 'undefined') {
|
|
newPlaylist.syncInfo = {
|
|
mediaSequence: oldPlaylist.mediaSequence + i,
|
|
time: lastRemovedSegment.start
|
|
};
|
|
this.logger_('playlist sync:', newPlaylist.syncInfo);
|
|
this.trigger('syncinfoupdate');
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Save the mapping from playlist's ProgramDateTime to display. This should
|
|
* only ever happen once at the start of playback.
|
|
*
|
|
* @param {Playlist} playlist - The currently active playlist
|
|
*/
|
|
}, {
|
|
key: 'setDateTimeMapping',
|
|
value: function setDateTimeMapping(playlist) {
|
|
if (!this.datetimeToDisplayTime && playlist.dateTimeObject) {
|
|
var playlistTimestamp = playlist.dateTimeObject.getTime() / 1000;
|
|
|
|
this.datetimeToDisplayTime = -playlistTimestamp;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reset the state of the inspection cache when we do a rendition
|
|
* switch
|
|
*/
|
|
}, {
|
|
key: 'reset',
|
|
value: function reset() {
|
|
this.inspectCache_ = undefined;
|
|
}
|
|
|
|
/**
|
|
* Probe or inspect a fmp4 or an mpeg2-ts segment to determine the start
|
|
* and end of the segment in it's internal "media time". Used to generate
|
|
* mappings from that internal "media time" to the display time that is
|
|
* shown on the player.
|
|
*
|
|
* @param {SegmentInfo} segmentInfo - The current active request information
|
|
*/
|
|
}, {
|
|
key: 'probeSegmentInfo',
|
|
value: function probeSegmentInfo(segmentInfo) {
|
|
var segment = segmentInfo.segment;
|
|
var playlist = segmentInfo.playlist;
|
|
var timingInfo = undefined;
|
|
|
|
if (segment.map) {
|
|
timingInfo = this.probeMp4Segment_(segmentInfo);
|
|
} else {
|
|
timingInfo = this.probeTsSegment_(segmentInfo);
|
|
}
|
|
|
|
if (timingInfo) {
|
|
if (this.calculateSegmentTimeMapping_(segmentInfo, timingInfo)) {
|
|
this.saveDiscontinuitySyncInfo_(segmentInfo);
|
|
|
|
// If the playlist does not have sync information yet, record that information
|
|
// now with segment timing information
|
|
if (!playlist.syncInfo) {
|
|
playlist.syncInfo = {
|
|
mediaSequence: playlist.mediaSequence + segmentInfo.mediaIndex,
|
|
time: segment.start
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
return timingInfo;
|
|
}
|
|
|
|
/**
|
|
* Probe an fmp4 or an mpeg2-ts segment to determine the start of the segment
|
|
* in it's internal "media time".
|
|
*
|
|
* @private
|
|
* @param {SegmentInfo} segmentInfo - The current active request information
|
|
* @return {object} The start and end time of the current segment in "media time"
|
|
*/
|
|
}, {
|
|
key: 'probeMp4Segment_',
|
|
value: function probeMp4Segment_(segmentInfo) {
|
|
var segment = segmentInfo.segment;
|
|
var timescales = _muxJsLibMp4Probe2['default'].timescale(segment.map.bytes);
|
|
var startTime = _muxJsLibMp4Probe2['default'].startTime(timescales, segmentInfo.bytes);
|
|
|
|
if (segmentInfo.timestampOffset !== null) {
|
|
segmentInfo.timestampOffset -= startTime;
|
|
}
|
|
|
|
return {
|
|
start: startTime,
|
|
end: startTime + segment.duration
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Probe an mpeg2-ts segment to determine the start and end of the segment
|
|
* in it's internal "media time".
|
|
*
|
|
* @private
|
|
* @param {SegmentInfo} segmentInfo - The current active request information
|
|
* @return {object} The start and end time of the current segment in "media time"
|
|
*/
|
|
}, {
|
|
key: 'probeTsSegment_',
|
|
value: function probeTsSegment_(segmentInfo) {
|
|
var timeInfo = (0, _muxJsLibToolsTsInspectorJs.inspect)(segmentInfo.bytes, this.inspectCache_);
|
|
var segmentStartTime = undefined;
|
|
var segmentEndTime = undefined;
|
|
|
|
if (!timeInfo) {
|
|
return null;
|
|
}
|
|
|
|
if (timeInfo.video && timeInfo.video.length === 2) {
|
|
this.inspectCache_ = timeInfo.video[1].dts;
|
|
segmentStartTime = timeInfo.video[0].dtsTime;
|
|
segmentEndTime = timeInfo.video[1].dtsTime;
|
|
} else if (timeInfo.audio && timeInfo.audio.length === 2) {
|
|
this.inspectCache_ = timeInfo.audio[1].dts;
|
|
segmentStartTime = timeInfo.audio[0].dtsTime;
|
|
segmentEndTime = timeInfo.audio[1].dtsTime;
|
|
}
|
|
|
|
return {
|
|
start: segmentStartTime,
|
|
end: segmentEndTime,
|
|
containsVideo: timeInfo.video && timeInfo.video.length === 2,
|
|
containsAudio: timeInfo.audio && timeInfo.audio.length === 2
|
|
};
|
|
}
|
|
}, {
|
|
key: 'timestampOffsetForTimeline',
|
|
value: function timestampOffsetForTimeline(timeline) {
|
|
if (typeof this.timelines[timeline] === 'undefined') {
|
|
return null;
|
|
}
|
|
return this.timelines[timeline].time;
|
|
}
|
|
}, {
|
|
key: 'mappingForTimeline',
|
|
value: function mappingForTimeline(timeline) {
|
|
if (typeof this.timelines[timeline] === 'undefined') {
|
|
return null;
|
|
}
|
|
return this.timelines[timeline].mapping;
|
|
}
|
|
|
|
/**
|
|
* Use the "media time" for a segment to generate a mapping to "display time" and
|
|
* save that display time to the segment.
|
|
*
|
|
* @private
|
|
* @param {SegmentInfo} segmentInfo
|
|
* The current active request information
|
|
* @param {object} timingInfo
|
|
* The start and end time of the current segment in "media time"
|
|
* @returns {Boolean}
|
|
* Returns false if segment time mapping could not be calculated
|
|
*/
|
|
}, {
|
|
key: 'calculateSegmentTimeMapping_',
|
|
value: function calculateSegmentTimeMapping_(segmentInfo, timingInfo) {
|
|
var segment = segmentInfo.segment;
|
|
var mappingObj = this.timelines[segmentInfo.timeline];
|
|
|
|
if (segmentInfo.timestampOffset !== null) {
|
|
this.logger_('tsO:', segmentInfo.timestampOffset);
|
|
|
|
mappingObj = {
|
|
time: segmentInfo.startOfSegment,
|
|
mapping: segmentInfo.startOfSegment - timingInfo.start
|
|
};
|
|
this.timelines[segmentInfo.timeline] = mappingObj;
|
|
this.trigger('timestampoffset');
|
|
|
|
segment.start = segmentInfo.startOfSegment;
|
|
segment.end = timingInfo.end + mappingObj.mapping;
|
|
} else if (mappingObj) {
|
|
segment.start = timingInfo.start + mappingObj.mapping;
|
|
segment.end = timingInfo.end + mappingObj.mapping;
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Each time we have discontinuity in the playlist, attempt to calculate the location
|
|
* in display of the start of the discontinuity and save that. We also save an accuracy
|
|
* value so that we save values with the most accuracy (closest to 0.)
|
|
*
|
|
* @private
|
|
* @param {SegmentInfo} segmentInfo - The current active request information
|
|
*/
|
|
}, {
|
|
key: 'saveDiscontinuitySyncInfo_',
|
|
value: function saveDiscontinuitySyncInfo_(segmentInfo) {
|
|
var playlist = segmentInfo.playlist;
|
|
var segment = segmentInfo.segment;
|
|
|
|
// If the current segment is a discontinuity then we know exactly where
|
|
// the start of the range and it's accuracy is 0 (greater accuracy values
|
|
// mean more approximation)
|
|
if (segment.discontinuity) {
|
|
this.discontinuities[segment.timeline] = {
|
|
time: segment.start,
|
|
accuracy: 0
|
|
};
|
|
} else if (playlist.discontinuityStarts.length) {
|
|
// Search for future discontinuities that we can provide better timing
|
|
// information for and save that information for sync purposes
|
|
for (var i = 0; i < playlist.discontinuityStarts.length; i++) {
|
|
var segmentIndex = playlist.discontinuityStarts[i];
|
|
var discontinuity = playlist.discontinuitySequence + i + 1;
|
|
var mediaIndexDiff = segmentIndex - segmentInfo.mediaIndex;
|
|
var accuracy = Math.abs(mediaIndexDiff);
|
|
|
|
if (!this.discontinuities[discontinuity] || this.discontinuities[discontinuity].accuracy > accuracy) {
|
|
var time = undefined;
|
|
|
|
if (mediaIndexDiff < 0) {
|
|
time = segment.start - (0, _playlist.sumDurations)(playlist, segmentInfo.mediaIndex, segmentIndex);
|
|
} else {
|
|
time = segment.end + (0, _playlist.sumDurations)(playlist, segmentInfo.mediaIndex + 1, segmentIndex);
|
|
}
|
|
|
|
this.discontinuities[discontinuity] = {
|
|
time: time,
|
|
accuracy: accuracy
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A debugging logger noop that is set to console.log only if debugging
|
|
* is enabled globally
|
|
*
|
|
* @private
|
|
*/
|
|
}, {
|
|
key: 'logger_',
|
|
value: function logger_() {}
|
|
}]);
|
|
|
|
return SyncController;
|
|
})(_videoJs2['default'].EventTarget);
|
|
|
|
exports['default'] = SyncController; |