'use strict'; var _vttparser = require('./vttparser'); var _vttparser2 = _interopRequireDefault(_vttparser); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } // String.prototype.startsWith is not supported in IE11 var startsWith = function startsWith(inputString, searchString, position) { return inputString.substr(position || 0, searchString.length) === searchString; }; var cueString2millis = function cueString2millis(timeString) { var ts = parseInt(timeString.substr(-3)); var secs = parseInt(timeString.substr(-6, 2)); var mins = parseInt(timeString.substr(-9, 2)); var hours = timeString.length > 9 ? parseInt(timeString.substr(0, timeString.indexOf(':'))) : 0; if (isNaN(ts) || isNaN(secs) || isNaN(mins) || isNaN(hours)) { return -1; } ts += 1000 * secs; ts += 60 * 1000 * mins; ts += 60 * 60 * 1000 * hours; return ts; }; // From https://github.com/darkskyapp/string-hash var hash = function hash(text) { var hash = 5381; var i = text.length; while (i) { hash = hash * 33 ^ text.charCodeAt(--i); } return (hash >>> 0).toString(); }; var calculateOffset = function calculateOffset(vttCCs, cc, presentationTime) { var currCC = vttCCs[cc]; var prevCC = vttCCs[currCC.prevCC]; // This is the first discontinuity or cues have been processed since the last discontinuity // Offset = current discontinuity time if (!prevCC || !prevCC.new && currCC.new) { vttCCs.ccOffset = vttCCs.presentationOffset = currCC.start; currCC.new = false; return; } // There have been discontinuities since cues were last parsed. // Offset = time elapsed while (prevCC && prevCC.new) { vttCCs.ccOffset += currCC.start - prevCC.start; currCC.new = false; currCC = prevCC; prevCC = vttCCs[currCC.prevCC]; } vttCCs.presentationOffset = presentationTime; }; var WebVTTParser = { parse: function parse(vttByteArray, syncPTS, vttCCs, cc, callBack, errorCallBack) { // Convert byteArray into string, replacing any somewhat exotic linefeeds with "\n", then split on that character. var re = /\r\n|\n\r|\n|\r/g; var vttLines = String.fromCharCode.apply(null, new Uint8Array(vttByteArray)).trim().replace(re, '\n').split('\n'); var cueTime = '00:00.000'; var mpegTs = 0; var localTime = 0; var presentationTime = 0; var cues = []; var parsingError = void 0; var inHeader = true; // let VTTCue = VTTCue || window.TextTrackCue; // Create parser object using VTTCue with TextTrackCue fallback on certain browsers. var parser = new _vttparser2.default(); parser.oncue = function (cue) { // Adjust cue timing; clamp cues to start no earlier than - and drop cues that don't end after - 0 on timeline. var currCC = vttCCs[cc]; var cueOffset = vttCCs.ccOffset; // Update offsets for new discontinuities if (currCC && currCC.new) { if (localTime !== undefined) { // When local time is provided, offset = discontinuity start time - local time cueOffset = vttCCs.ccOffset = currCC.start; } else { calculateOffset(vttCCs, cc, presentationTime); } } if (presentationTime) { // If we have MPEGTS, offset = presentation time + discontinuity offset cueOffset = presentationTime + vttCCs.ccOffset - vttCCs.presentationOffset; } cue.startTime += cueOffset - localTime; cue.endTime += cueOffset - localTime; // Create a unique hash id for a cue based on start/end times and text. // This helps timeline-controller to avoid showing repeated captions. cue.id = hash(cue.startTime) + hash(cue.endTime) + hash(cue.text); // Fix encoding of special characters. TODO: Test with all sorts of weird characters. cue.text = decodeURIComponent(escape(cue.text)); if (cue.endTime > 0) { cues.push(cue); } }; parser.onparsingerror = function (e) { parsingError = e; }; parser.onflush = function () { if (parsingError && errorCallBack) { errorCallBack(parsingError); return; } callBack(cues); }; // Go through contents line by line. vttLines.forEach(function (line) { if (inHeader) { // Look for X-TIMESTAMP-MAP in header. if (startsWith(line, 'X-TIMESTAMP-MAP=')) { // Once found, no more are allowed anyway, so stop searching. inHeader = false; // Extract LOCAL and MPEGTS. line.substr(16).split(',').forEach(function (timestamp) { if (startsWith(timestamp, 'LOCAL:')) { cueTime = timestamp.substr(6); } else if (startsWith(timestamp, 'MPEGTS:')) { mpegTs = parseInt(timestamp.substr(7)); } }); try { // Calculate subtitle offset in milliseconds. // If sync PTS is less than zero, we have a 33-bit wraparound, which is fixed by adding 2^33 = 8589934592. syncPTS = syncPTS < 0 ? syncPTS + 8589934592 : syncPTS; // Adjust MPEGTS by sync PTS. mpegTs -= syncPTS; // Convert cue time to seconds localTime = cueString2millis(cueTime) / 1000; // Convert MPEGTS to seconds from 90kHz. presentationTime = mpegTs / 90000; if (localTime === -1) { parsingError = new Error('Malformed X-TIMESTAMP-MAP: ' + line); } } catch (e) { parsingError = new Error('Malformed X-TIMESTAMP-MAP: ' + line); } // Return without parsing X-TIMESTAMP-MAP line. return; } else if (line === '') { inHeader = false; } } // Parse line by default. parser.parse(line + '\n'); }); parser.flush(); } }; module.exports = WebVTTParser;