diff --git a/app/animeHandler.js b/app/animeHandler.js index ab878d7..7ecd90d 100644 --- a/app/animeHandler.js +++ b/app/animeHandler.js @@ -207,11 +207,13 @@ function viewAnime(media) { document.querySelector(".view .banner img").src = "" document.querySelector(".view .banner img").src = media.bannerImage document.querySelector(".view .contain-img").src = media.coverImage.extraLarge - document.querySelector(".view .title").textContent = !!media.title.english ? media.title.english : media.title.romaji - document.querySelector(".view .desc").innerHTML = !!media.description ? media.description : "" + document.querySelector(".view .title").textContent = media.title.english || media.title.romaji + document.querySelector(".view .desc").innerHTML = media.description || "" document.querySelector(".view .details").innerHTML = "" document.querySelector(".view #play").onclick = function () { nyaaSearch(media, document.querySelector(".view #ep").value); halfmoon.toggleModal("view") } detailsCreator(media) + document.querySelector(".view #ep").value = 1 + document.querySelector(".view #ep").max = media.episodes || 999 document.querySelector(".view .details").appendChild(detailsfrag) } diff --git a/app/app.html b/app/app.html index de6293a..c49d0f2 100644 --- a/app/app.html +++ b/app/app.html @@ -56,7 +56,7 @@
-
+
@@ -216,10 +216,8 @@
-
-
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 += ''; - 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 += ''; + 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 += ''; } } - } 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 += ''; + // 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 += ''; + } + 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 += ''; - } - 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.