![]()
-
diff --git a/app/bundle.js b/app/bundle.js
index 3e87ba6..a1938e3 100644
--- a/app/bundle.js
+++ b/app/bundle.js
@@ -13069,245 +13069,251 @@ if (typeof Object.create === 'function') {
},{}],14:[function(require,module,exports){
(function (Buffer){
const Transform = require('readable-stream').Transform
- const ebml = require('ebml')
- const ebmlBlock = require('ebml-block')
- const readElement = require('./lib/read-element')
-
- // track elements we care about
- const TRACK_ELEMENTS = ['TrackNumber', 'TrackType', 'Language', 'CodecID', 'CodecPrivate']
- const SUBTITLE_TYPES = ['S_TEXT/UTF8', 'S_TEXT/SSA', 'S_TEXT/ASS']
- const ASS_KEYS = ['readOrder', 'layer', 'style', 'name', 'marginL', 'marginR', 'marginV', 'effect', 'text']
-
- const CUES_ID = Buffer.from('1C53BB6B', 'hex')
-
- class MatroskaSubtitles extends Transform {
- constructor ({ prevInstance, offset } = {}) {
- super()
-
- let currentTrack = null
- let currentSubtitleBlock = null
- let currentClusterTimecode = null
-
- let currentSeekID = null
-
- let waitForNext = false
-
- this.decoder = new ebml.Decoder()
-
- if (prevInstance instanceof MatroskaSubtitles) {
- if (offset == null) throw new Error('no offset')
-
- prevInstance.once('drain', () => {
- // prevInstance.end()
- console.log('prevInstance drained')
- })
-
- if (offset === 0) {
- // just begin normal parsing
- this.subtitleTracks = prevInstance.subtitleTracks || new Map()
- this.timecodeScale = prevInstance.timecodeScale || 1
- this.cues = prevInstance.cues
-
- this.decoder.on('data', _onMetaData.bind(this))
- return
- }
-
- // copy previous metadata
- this.subtitleTracks = prevInstance.subtitleTracks
- this.timecodeScale = prevInstance.timecodeScale
+const ebml = require('ebml')
+const ebmlBlock = require('ebml-block')
+const readElement = require('./lib/read-element')
+
+// track elements we care about
+const TRACK_ELEMENTS = ['TrackNumber', 'TrackType', 'Language', 'CodecID', 'CodecPrivate']
+const SUBTITLE_TYPES = ['S_TEXT/UTF8', 'S_TEXT/SSA', 'S_TEXT/ASS']
+const ASS_KEYS = ['readOrder', 'layer', 'style', 'name', 'marginL', 'marginR', 'marginV', 'effect', 'text']
+
+const CUES_ID = Buffer.from('1C53BB6B', 'hex')
+
+class MatroskaSubtitles extends Transform {
+ constructor ({ prevInstance, offset } = {}) {
+ super()
+
+ let currentTrack = null
+ let currentSubtitleBlock = null
+ let currentClusterTimecode = null
+
+ let currentSeekID = null
+
+ let waitForNext = false
+
+ this.decoder = new ebml.Decoder()
+
+ if (prevInstance instanceof MatroskaSubtitles) {
+ if (offset == null) throw new Error('no offset')
+
+ prevInstance.once('drain', () => {
+ // prevInstance.end()
+ console.log('prevInstance drained')
+ })
+
+ if (offset === 0) {
+ // just begin normal parsing
+ this.subtitleTracks = prevInstance.subtitleTracks || new Map()
+ this.timecodeScale = prevInstance.timecodeScale || 1
this.cues = prevInstance.cues
-
- if (!this.cues) {
- this.decoder = null
- return console.warn('No cues was parsed. Subtitle parsing disabled.')
- }
-
- // find a cue that's close to the file offset
- // const cueArray = Uint32Array.from(this.cues.positions)
- // cueArray.sort()
- const cueArray = Array.from(this.cues.positions)
- cueArray.sort((a, b) => a - b)
-
- const closestCue = cueArray.find(i => i >= offset)
-
- if (closestCue != null) {
- // prepare to skip file stream until we hit a cue position
- this.skip = closestCue - offset
- // set internal decoder position to output consistent file offsets
- this.decoder.total = closestCue
-
- // console.log('using cue:', closestCue)
-
- this.decoder.on('data', _onMetaData.bind(this))
- } else {
- this.decoder = null
- console.warn(`No cues for offset ${offset}. Subtitle parsing disabled.`)
- }
- } else {
- if (offset) {
- this.decoder = null
- console.error(`Offset is ${offset}, and must be 0 for initial instance. Subtitle parsing disabled.`)
- return
- }
-
- this.subtitleTracks = new Map()
- this.timecodeScale = 1
-
+
this.decoder.on('data', _onMetaData.bind(this))
+ return
}
-
- function _onMetaData (chunk) {
- if (waitForNext) {
- waitForNext = false
- // Keep cues if this is the same segment
- if (!this.cues || this.cues.start !== chunk[1].start) {
- this.cues = { start: chunk[1].start, positions: new Set() }
- }
- }
-
- if (chunk[0] === 'start' && chunk[1].name === 'Segment') {
- // TODO: only record first segment?
- // TODO: find a simpler way to do this
- waitForNext = true
- }
-
- if (chunk[1].name === 'SeekID') {
- // TODO: .value is undefined for some reason?
- currentSeekID = chunk[1].data
- }
-
- if (currentSeekID && chunk[1].name === 'SeekPosition') {
- if (CUES_ID.equals(currentSeekID)) {
- // hack: this is not a cue position, but the position to the cue data itself,
- // in case it's not located at the beginning of the file.
- this.cues.positions.add(this.cues.start + chunk[1].value)
- }
- }
-
- if (chunk[1].name === 'CueClusterPosition') {
- this.cues.positions.add(this.cues.start + chunk[1].value)
- }
-
- if (chunk[0] === 'end' && chunk[1].name === 'Cues') {
- this.emit('cues')
- }
-
- // Segment Information
- if (chunk[1].name === 'TimecodeScale') {
- this.timecodeScale = readElement(chunk[1]) / 1000000
- }
-
- // Tracks
- if (chunk[0] === 'start' && chunk[1].name === 'TrackEntry') {
- currentTrack = {}
- }
-
- if (currentTrack && chunk[0] === 'tag') {
- // save info about track currently being scanned
- if (TRACK_ELEMENTS.includes(chunk[1].name)) {
- currentTrack[chunk[1].name] = readElement(chunk[1])
- }
- }
-
- if (chunk[0] === 'end' && chunk[1].name === 'TrackEntry') {
- if (currentTrack.TrackType === 0x11) { // Subtitle Track
- if (SUBTITLE_TYPES.includes(currentTrack.CodecID)) {
- const track = {
- number: currentTrack.TrackNumber,
- language: currentTrack.Language,
- type: currentTrack.CodecID.substring(7).toLowerCase()
- }
-
- if (currentTrack.CodecPrivate) {
- // only SSA/ASS
- track.header = currentTrack.CodecPrivate.toString('utf8')
- }
-
- this.subtitleTracks.set(currentTrack.TrackNumber, track)
- }
- }
- currentTrack = null
- }
-
- if (chunk[0] === 'end' && chunk[1].name === 'Tracks') {
- // this.decoder.removeListener('data', _onMetaData)
-
- // if (this.subtitleTracks.size <= 0) return this.end()
-
- // this.decoder.on('data', _onClusterData)
- this.emit('tracks', Array.from(this.subtitleTracks.values()))
- }
- // }
-
- // function _onClusterData (chunk) {
- // TODO: assuming this is a Cluster `Timecode`
- if (chunk[1].name === 'Timecode') {
- currentClusterTimecode = readElement(chunk[1])
- }
-
- if (chunk[1].name === 'Block') {
- const block = ebmlBlock(chunk[1].data)
-
- if (this.subtitleTracks.has(block.trackNumber)) {
- const type = this.subtitleTracks.get(block.trackNumber).type
-
- const subtitle = {
- text: block.frames[0].toString('utf8'),
- time: (block.timecode + currentClusterTimecode) * this.timecodeScale
- }
-
- if (type === 'ass' || type === 'ssa') {
- // extract SSA/ASS keys
- const values = subtitle.text.split(',')
- // ignore read-order, and skip layer if ssa
- let i = type === 'ssa' ? 2 : 1
- for (; i < 9; i++) {
- subtitle[ASS_KEYS[i]] = values[i]
- }
- // re-append extra text that might have been split
- for (i = 9; i < values.length; i++) {
- subtitle.text += ',' + values[i]
- }
- }
-
- currentSubtitleBlock = [subtitle, block.trackNumber]
- }
- }
-
- // TODO: assuming `BlockDuration` exists and always comes after `Block`
- if (currentSubtitleBlock && chunk[1].name === 'BlockDuration') {
- currentSubtitleBlock[0].duration = readElement(chunk[1]) * this.timecodeScale
-
- this.emit('subtitle', ...currentSubtitleBlock)
-
- currentSubtitleBlock = null
- }
+
+ // copy previous metadata
+ this.subtitleTracks = prevInstance.subtitleTracks
+ this.timecodeScale = prevInstance.timecodeScale
+ this.cues = prevInstance.cues
+
+ if (!this.cues) {
+ this.decoder = null
+ return console.warn('No cues was parsed. Subtitle parsing disabled.')
}
- }
-
- _transform (chunk, _, callback) {
- if (!this.decoder) return callback(null, chunk)
-
- if (this.skip) {
- // skip bytes to reach cue position
- if (this.skip < chunk.length) {
- // slice chunk
- const sc = chunk.slice(this.skip)
- this.skip = 0
- this.decoder.write(sc)
- } else {
- // skip entire chunk
- this.skip -= chunk.length
- }
+
+ // find a cue that's close to the file offset
+ // const cueArray = Uint32Array.from(this.cues.positions)
+ // cueArray.sort()
+ const cueArray = Array.from(this.cues.positions)
+ cueArray.sort((a, b) => a - b)
+
+ const closestCue = cueArray.find(i => i >= offset)
+
+ if (closestCue != null) {
+ // prepare to skip file stream until we hit a cue position
+ this.skip = closestCue - offset
+ // set internal decoder position to output consistent file offsets
+ this.decoder.total = closestCue
+
+ // console.log('using cue:', closestCue)
+
+ this.decoder.on('data', _onMetaData.bind(this))
} else {
- this.decoder.write(chunk)
+ this.decoder = null
+ console.warn(`No cues for offset ${offset}. Subtitle parsing disabled.`)
+ }
+ } else {
+ if (offset) {
+ this.decoder = null
+ console.error(`Offset is ${offset}, and must be 0 for initial instance. Subtitle parsing disabled.`)
+ return
+ }
+
+ this.subtitleTracks = new Map()
+ this.timecodeScale = 1
+
+ this.decoder.on('data', _onMetaData.bind(this))
+ }
+
+ function _onMetaData (chunk) {
+ if (waitForNext) {
+ waitForNext = false
+ // Keep cues if this is the same segment
+ if (!this.cues) {
+ this.cues = { start: chunk[1].start, positions: new Set() }
+ } else if (this.cues.start !== chunk[1].start) {
+ this.cues = { start: chunk[1].start, positions: new Set() }
+ console.warn('New segment found - resetting cues! Not sure we can handle this!?')
+ } else {
+ console.info('Saw first segment again. Keeping cues.')
+ }
+ }
+
+ if (chunk[0] === 'start' && chunk[1].name === 'Segment') {
+ // TODO: only record first segment?
+ // TODO: find a simpler way to do this
+ waitForNext = true
+ }
+
+ if (chunk[1].name === 'SeekID') {
+ // TODO: .value is undefined for some reason?
+ currentSeekID = chunk[1].data
+ }
+
+ if (currentSeekID && chunk[1].name === 'SeekPosition') {
+ //if (CUES_ID.equals(currentSeekID)) {
+ // hack: this is not a cue position, but the position to the cue data itself,
+ // in case it's not located at the beginning of the file.
+ // actually, just add all seek positions.
+ this.cues.positions.add(this.cues.start + chunk[1].value)
+ //}
+ }
+
+ if (chunk[1].name === 'CueClusterPosition') {
+ this.cues.positions.add(this.cues.start + chunk[1].value)
+ }
+
+ if (chunk[0] === 'end' && chunk[1].name === 'Cues') {
+ this.emit('cues')
+ }
+
+ // Segment Information
+ if (chunk[1].name === 'TimecodeScale') {
+ this.timecodeScale = readElement(chunk[1]) / 1000000
+ }
+
+ // Tracks
+ if (chunk[0] === 'start' && chunk[1].name === 'TrackEntry') {
+ currentTrack = {}
+ }
+
+ if (currentTrack && chunk[0] === 'tag') {
+ // save info about track currently being scanned
+ if (TRACK_ELEMENTS.includes(chunk[1].name)) {
+ currentTrack[chunk[1].name] = readElement(chunk[1])
+ }
+ }
+
+ if (chunk[0] === 'end' && chunk[1].name === 'TrackEntry') {
+ if (currentTrack.TrackType === 0x11) { // Subtitle Track
+ if (SUBTITLE_TYPES.includes(currentTrack.CodecID)) {
+ const track = {
+ number: currentTrack.TrackNumber,
+ language: currentTrack.Language,
+ type: currentTrack.CodecID.substring(7).toLowerCase()
+ }
+
+ if (currentTrack.CodecPrivate) {
+ // only SSA/ASS
+ track.header = currentTrack.CodecPrivate.toString('utf8')
+ }
+
+ this.subtitleTracks.set(currentTrack.TrackNumber, track)
+ }
+ }
+ currentTrack = null
+ }
+
+ if (chunk[0] === 'end' && chunk[1].name === 'Tracks') {
+ // this.decoder.removeListener('data', _onMetaData)
+
+ // if (this.subtitleTracks.size <= 0) return this.end()
+
+ // this.decoder.on('data', _onClusterData)
+ this.emit('tracks', Array.from(this.subtitleTracks.values()))
+ }
+ // }
+
+ // function _onClusterData (chunk) {
+ // TODO: assuming this is a Cluster `Timecode`
+ if (chunk[1].name === 'Timecode') {
+ currentClusterTimecode = readElement(chunk[1])
+ }
+
+ if (chunk[1].name === 'Block') {
+ const block = ebmlBlock(chunk[1].data)
+
+ if (this.subtitleTracks.has(block.trackNumber)) {
+ const type = this.subtitleTracks.get(block.trackNumber).type
+
+ const subtitle = {
+ text: block.frames[0].toString('utf8'),
+ time: (block.timecode + currentClusterTimecode) * this.timecodeScale
+ }
+
+ if (type === 'ass' || type === 'ssa') {
+ // extract SSA/ASS keys
+ const values = subtitle.text.split(',')
+ // ignore read-order, and skip layer if ssa
+ let i = type === 'ssa' ? 2 : 1
+ for (; i < 9; i++) {
+ subtitle[ASS_KEYS[i]] = values[i]
+ }
+ // re-append extra text that might have been split
+ for (i = 9; i < values.length; i++) {
+ subtitle.text += ',' + values[i]
+ }
+ }
+
+ currentSubtitleBlock = [subtitle, block.trackNumber]
+ }
+ }
+
+ // TODO: assuming `BlockDuration` exists and always comes after `Block`
+ if (currentSubtitleBlock && chunk[1].name === 'BlockDuration') {
+ currentSubtitleBlock[0].duration = readElement(chunk[1]) * this.timecodeScale
+
+ this.emit('subtitle', ...currentSubtitleBlock)
+
+ currentSubtitleBlock = null
}
-
- callback(null, chunk)
}
}
-
- module.exports = MatroskaSubtitles
+
+ _transform (chunk, _, callback) {
+ if (!this.decoder) return callback(null, chunk)
+
+ if (this.skip) {
+ // skip bytes to reach cue position
+ if (this.skip < chunk.length) {
+ // slice chunk
+ const sc = chunk.slice(this.skip)
+ this.skip = 0
+ this.decoder.write(sc)
+ } else {
+ // skip entire chunk
+ this.skip -= chunk.length
+ }
+ } else {
+ this.decoder.write(chunk)
+ }
+
+ callback(null, chunk)
+ }
+}
+
+module.exports = MatroskaSubtitles
}).call(this,require("buffer").Buffer)
},{"./lib/read-element":15,"buffer":3,"ebml":12,"ebml-block":9,"readable-stream":31}],15:[function(require,module,exports){
diff --git a/app/css2.css b/app/css2.css
index e54e7ff..a3b9e3a 100644
--- a/app/css2.css
+++ b/app/css2.css
@@ -283,4 +283,5 @@ section {
section:target {
display: block;
+ height: 100%
}
\ No newline at end of file
diff --git a/app/subtitletest.js b/app/subtitletest.js
index 9fdcea9..1fcf8a0 100644
--- a/app/subtitletest.js
+++ b/app/subtitletest.js
@@ -1,114 +1,147 @@
-let tracks = []
+let tracks = [],
+ parser
-let re_newline = /\\N/g, // replace \N with newline
+
+function parseSubs(range, stream) {
+ if (video.src.endsWith(".mkv")) {
+ console.log('set parser', range)
+
+ parser = new MatroskaSubtitles({ prevInstance: parser, offset: range.start })
+
+ parser.once('tracks', function (pTracks) {
+ console.log(pTracks)
+ tracks = []
+ pTracks.forEach(track => {
+ tracks[track.number] = video.addTextTrack('captions', track.type, track.language || track.number)
+ })
+ if (video.textTracks[0]) {
+ video.textTracks[0].mode = "showing"
+ }
+ })
+
+ parser.once('cues', function () {
+ console.log('seeking ready')
+ })
+
+ parser.on('subtitle', function (subtitle, trackNumber) {
+ subConvt(subtitle, trackNumber)
+ })
+ stream.pipe(parser)
+ }
+}
+const re_newline = /\\N/g, // replace \N with newline
re_softbreak = /\\n/g, // There's no equivalent function in WebVTT.
re_hardspace = /\\h/g, // Replace with
re_style = /\{([^}]+)\}/; // replace style
function subConvt(result, trackNumber) {
let cue = new VTTCue(result.time / 1000, (result.time + result.duration) / 1000, ""),
text = result.text;
- // Support for special characters in WebVTT.
- // For obvious reasons, the ampersand one *must* be first.
- text = text.replace(/&/g, "&").replace(//g, ">");
- let style, tagsToClose = []; // Places to stash style info.
- // Subtitles may contain any number of override tags, so we'll loop through
- // to find them all.
- while ((style = text.match(re_style))) {
- let tagsToOpen = [], replaceString = '';
- if (style[1] && style[1].split) { // Stop throwing errors on empty tags.
- style = style[1].split("\\"); // Get an array of override commands.
- for (let j = 1; j < style.length; j++) {
- // Extract the current tag name.
- let tagCommand = style[j].match(/[a-zA-Z]+/)[0];
- // Give special reckognition to one-letter tags.
- let oneLetter = (tagCommand.length == 1) ? tagCommand : "";
- // "New" position commands. It is assumed that bottom center position is the default.
- if (tagCommand === "an") {
- let posNum = Number(style[j].substring(2, 3));
- if (Math.floor((posNum - 1) / 3) == 1) {
- cue.line = 0.5;
- } else if (Math.floor((posNum - 1) / 3) == 2) {
- cue.line = 0;
- }
- if (posNum % 3 == 1) {
- cue.align = "start";
- cue.text = " \r\n"
- } else if (posNum % 3 == 0) {
- cue.align = "end";
- }
- // Legacy position commands.
- } else if (oneLetter === "a" && !Number.isNaN(Number(style[j].substring(1, 2)))) {
- let posNum = Number(style[j].substring(1, 2));
- if (posNum > 8) {
- cue.line = 0.5;
- } else if (posNum > 4) {
- cue.line = 0;
- }
- if ((posNum - 1) % 4 == 0) {
- cue.align = "start";
- cue.text = " \r\n"
- } else if ((posNum - 1) % 4 == 2) {
- cue.align = "end";
- }
- // Map simple text decoration commands to equivalent WebVTT text tags.
- // NOTE: Strikethrough (the 's' tag) is not supported in WebVTT.
- } else if (['b', 'i', 'u', 's'].includes(oneLetter)) {
- if (Number(style[j].substring(1, 2)) === 0
- // The more elaborate 'b-tag', which we will treat as an on-off selector.
- || (style[j].match(/b\d{3}/)
- && Number(style[j].match(/b(\d{3})/)[1]) < 500)
- ) {
- // Closing a tag.
- if (tagsToClose.includes(oneLetter)) {
- // Nothing needs to be done if this tag isn't already open.
- // HTML tags must be nested, so we must ensure that any tag nested inside
- // the tag being closed are also closed, and then opened again once the
- // current tag is closed.
- while (tagsToClose.length > 0) {
- let nowClosing = tagsToClose.pop();
- replaceString += '' + nowClosing + '>';
- if (nowClosing !== oneLetter) {
- tagsToOpen.push(nowClosing);
- } else {
- // There's no need to close the tags that the current tag
- // is nested within.
- break;
+ if (tracks[trackNumber].label == "ass") {
+ // Support for special characters in WebVTT.
+ // For obvious reasons, the ampersand one *must* be first.
+ text = text.replace(/&/g, "&").replace(//g, ">");
+ let style, tagsToClose = []; // Places to stash style info.
+ // Subtitles may contain any number of override tags, so we'll loop through
+ // to find them all.
+ while ((style = text.match(re_style))) {
+ let tagsToOpen = [], replaceString = '';
+ if (style[1] && style[1].split) { // Stop throwing errors on empty tags.
+ style = style[1].split("\\"); // Get an array of override commands.
+ for (let j = 1; j < style.length; j++) {
+ // Extract the current tag name.
+ let tagCommand = style[j].match(/[a-zA-Z]+/)[0];
+ // Give special reckognition to one-letter tags.
+ let oneLetter = (tagCommand.length == 1) ? tagCommand : "";
+ // "New" position commands. It is assumed that bottom center position is the default.
+ if (tagCommand === "an") {
+ let posNum = Number(style[j].substring(2, 3));
+ if (Math.floor((posNum - 1) / 3) == 1) {
+ cue.line = 0.5;
+ } else if (Math.floor((posNum - 1) / 3) == 2) {
+ cue.line = 0;
+ }
+ if (posNum % 3 == 1) {
+ cue.align = "start";
+ cue.text = " \r\n"
+ } else if (posNum % 3 == 0) {
+ cue.align = "end";
+ }
+ // Legacy position commands.
+ } else if (oneLetter === "a" && !Number.isNaN(Number(style[j].substring(1, 2)))) {
+ let posNum = Number(style[j].substring(1, 2));
+ if (posNum > 8) {
+ cue.line = 0.5;
+ } else if (posNum > 4) {
+ cue.line = 0;
+ }
+ if ((posNum - 1) % 4 == 0) {
+ cue.align = "start";
+ cue.text = " \r\n"
+ } else if ((posNum - 1) % 4 == 2) {
+ cue.align = "end";
+ }
+ // Map simple text decoration commands to equivalent WebVTT text tags.
+ // NOTE: Strikethrough (the 's' tag) is not supported in WebVTT.
+ } else if (['b', 'i', 'u', 's'].includes(oneLetter)) {
+ if (Number(style[j].substring(1, 2)) === 0
+ // The more elaborate 'b-tag', which we will treat as an on-off selector.
+ || (style[j].match(/b\d{3}/)
+ && Number(style[j].match(/b(\d{3})/)[1]) < 500)
+ ) {
+ // Closing a tag.
+ if (tagsToClose.includes(oneLetter)) {
+ // Nothing needs to be done if this tag isn't already open.
+ // HTML tags must be nested, so we must ensure that any tag nested inside
+ // the tag being closed are also closed, and then opened again once the
+ // current tag is closed.
+ while (tagsToClose.length > 0) {
+ let nowClosing = tagsToClose.pop();
+ replaceString += '' + nowClosing + '>';
+ if (nowClosing !== oneLetter) {
+ tagsToOpen.push(nowClosing);
+ } else {
+ // There's no need to close the tags that the current tag
+ // is nested within.
+ break;
+ }
}
}
+ } else {
+ // Opening a tag.
+ if (!tagsToClose.includes(oneLetter)) {
+ // Nothing needs to be done if the tag is already open.
+ // If no, place the tag on the bottom of the stack of tags being opened.
+ tagsToOpen.splice(0, 0, oneLetter);
+ }
}
- } else {
- // Opening a tag.
- if (!tagsToClose.includes(oneLetter)) {
- // Nothing needs to be done if the tag is already open.
- // If no, place the tag on the bottom of the stack of tags being opened.
- tagsToOpen.splice(0, 0, oneLetter);
+ } else if (oneLetter === 'r') {
+ // Resetting override tags, by closing all open tags.
+ // TODO: The 'r' tag can also be used to switch to a different named style,
+ // however, named styles haven't been implemented.
+ while (tagsToClose.length > 0) {
+ replaceString += '' + tagsToClose.pop() + '>';
}
}
- } else if (oneLetter === 'r') {
- // Resetting override tags, by closing all open tags.
- // TODO: The 'r' tag can also be used to switch to a different named style,
- // however, named styles haven't been implemented.
- while (tagsToClose.length > 0) {
- replaceString += '' + tagsToClose.pop() + '>';
+ // Insert open-tags for tags in the to-open list.
+ while (tagsToOpen.length > 0) {
+ let nowOpening = tagsToOpen.pop();
+ replaceString += '<' + nowOpening + '>';
+ tagsToClose.push(nowOpening);
}
}
- // Insert open-tags for tags in the to-open list.
- while (tagsToOpen.length > 0) {
- let nowOpening = tagsToOpen.pop();
- replaceString += '<' + nowOpening + '>';
- tagsToClose.push(nowOpening);
- }
}
+ text = text.replace(re_style, replaceString); // Replace override tag.
}
- text = text.replace(re_style, replaceString); // Replace override tag.
+ text = text.replace(re_newline, "\r\n").replace(re_softbreak, " ").replace(
+ re_hardspace, " ");
+ let content = "
" + text
+ while (tagsToClose.length > 0) {
+ content += '' + tagsToClose.pop() + '>';
+ }
+ cue.text += `${content}\r\n `
+ } else {
+ cue.text = `${text}\r\n `
}
- text = text.replace(re_newline, "\r\n").replace(re_softbreak, " ").replace(
- re_hardspace, " ");
- let content = "" + text
- while (tagsToClose.length > 0) {
- content += '' + tagsToClose.pop() + '>';
- }
- cue.text += `${content}\r\n `
if (!Object.values(tracks[trackNumber].cues).some(c => c.text == cue.text)) {
tracks[trackNumber].addCue(cue)
}
diff --git a/app/torrentHandler.js b/app/torrentHandler.js
index e54a03e..68f0cdb 100644
--- a/app/torrentHandler.js
+++ b/app/torrentHandler.js
@@ -24,7 +24,6 @@ const client = new WebTorrent(),
],
scope = '/app/',
sw = navigator.serviceWorker.register('sw.js', { scope })
-let parser
//for debugging
function t(a) {
switch (a) {
@@ -47,7 +46,7 @@ WEBTORRENT_ANNOUNCE = announceList
return url.indexOf('wss://') === 0 || url.indexOf('ws://') === 0
})
var nowPlaying,
- maxTorrents = 3,
+ maxTorrents = 1,
subStream
function addTorrent(magnet) {
if (client.torrents.length >= maxTorrents) {
@@ -56,6 +55,7 @@ function addTorrent(magnet) {
halfmoon.toggleModal("tsearch")
document.location.href = "#player"
client.add(magnet, async function (torrent) {
+ video.src = ""
await sw
function onProgress() {
peers.textContent = torrent.numPeers
@@ -90,6 +90,7 @@ function addTorrent(magnet) {
videoFile = file
}
})
+ parser = undefined
video.src = `${scope}webtorrent/${torrent.infoHash}/${encodeURI(videoFile.path)}`
if (subStream) {
subStream.destroy()
@@ -128,36 +129,14 @@ function serveFile(file, req) {
}
- res.headers['Cache-Control'] = 'no-store'
+ res.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate, max-age=0'
+ res.headers['Expires'] = '0'
res.body = req.method === 'HEAD' ? '' : 'stream'
- console.log('set parser', range)
-
- parser = new MatroskaSubtitles({ prevInstance: parser, offset: range.start })
-
- parser.once('tracks', function (pTracks) {
- tracks = []
- pTracks.forEach(track => {
- if (track.type == "ass") {
- tracks[track.number] = video.addTextTrack('captions', track.number, !!track.language ? track.language : track.number)
- }
- })
- if (video.textTracks[0]) {
- video.textTracks[0].mode = "showing"
- }
- })
-
- parser.once('cues', function () {
- console.log('seeking ready')
- })
-
- parser.on('subtitle', function (subtitle, trackNumber) {
- subConvt(subtitle, trackNumber)
- })
-
// parser is really a passthrough mkv stream now
- file.createReadStream(range).pipe(parser)
+ let stream = file.createReadStream(range)
+ parseSubs(range, stream)
- return [res, req.method === 'GET' && parser]
+ return [res, req.method === 'GET' && parser || stream]
}
// kind of a fetch event from service worker but for the main thread.