diff --git a/src/playback/dash/DASHHandler.ts b/src/playback/dash/DASHHandler.ts index 7542448a..02c84685 100644 --- a/src/playback/dash/DASHHandler.ts +++ b/src/playback/dash/DASHHandler.ts @@ -98,7 +98,7 @@ export class DASHHandler extends Transform { `Selected: id=${selected.id}, codecs=${selected.codecs}, bandwidth=${selected.bandwidth}` ) - const initRes = await makeRequest(selected.initUrl, { + const initRes = await makeRequest(this._resolveUrl(selected.initUrl), { headers: this.options.headers, method: 'GET', localAddress: this.options.localAddress, @@ -295,11 +295,24 @@ export class DASHHandler extends Transform { let segNum = selected.startNumber for (const segGroup of selected.segments) { for (let r = 0; r <= segGroup.repeat; r++) { - yield selected.mediaTemplate.replace('$Number$', String(segNum++)) + yield this._resolveUrl( + selected.mediaTemplate.replace('$Number$', String(segNum++)) + ) } } } + /** + * Resolves a potentially relative URL against the MPD location. + */ + private _resolveUrl(url: string): string { + try { + return new URL(url, this.mpdUrl).href + } catch { + return url + } + } + private _countSegments(selected: DASHRepresentation): number { let count = 0 for (const segGroup of selected.segments) { diff --git a/src/playback/demuxers/WebmOpus.ts b/src/playback/demuxers/WebmOpus.ts index 45f62d34..b84621c2 100644 --- a/src/playback/demuxers/WebmOpus.ts +++ b/src/playback/demuxers/WebmOpus.ts @@ -252,8 +252,8 @@ abstract class WebmBaseDemuxer extends Transform { const skipped = this.ringBuffer.length if (skipped > 0) webmOpusProfiler.skippedBytes += skipped this.skipUntil = res._skipUntil - this.ringBuffer.skip(this.ringBuffer.length) - this.processed += BigInt(this.ringBuffer.length) + this.ringBuffer.skip(skipped) + this.processed += BigInt(skipped) break } diff --git a/src/playback/hls/HLSHandler.ts b/src/playback/hls/HLSHandler.ts index 84cf527f..2420de3e 100644 --- a/src/playback/hls/HLSHandler.ts +++ b/src/playback/hls/HLSHandler.ts @@ -387,6 +387,17 @@ export default class HLSHandler extends PassThrough { }) } + if ( + !this.isLive && + this.segmentQueue.length === 0 && + !this.isFetching && + !this.stop && + !this.destroyed + ) { + this.end() + return + } + if (this.isLive && !playlistContent.includes('#EXT-X-ENDLIST')) { this._scheduleNextTick(parsed.targetDuration) } diff --git a/src/playback/processing/CrossfadeController.ts b/src/playback/processing/CrossfadeController.ts index 66d3b845..7f6dfad3 100644 --- a/src/playback/processing/CrossfadeController.ts +++ b/src/playback/processing/CrossfadeController.ts @@ -89,7 +89,9 @@ interface ReverbTailState { allpassLeft: Float32Array allpassRight: Float32Array posComb1: number + posComb1Right: number posComb2: number + posComb2Right: number posAllpass: number } @@ -102,7 +104,9 @@ function createReverbTailState(): ReverbTailState { allpassLeft: new Float32Array(556), allpassRight: new Float32Array(556), posComb1: 0, + posComb1Right: 0, posComb2: 0, + posComb2Right: 0, posAllpass: 0 } } @@ -1007,17 +1011,17 @@ export class CrossfadeController extends Transform { const c1L = rev.comb1Left[rev.posComb1] ?? 0 rev.comb1Left[rev.posComb1] = inputLeft + c1L * feedback - const c1R = rev.comb1Right[rev.posComb1 % rev.comb1Right.length] ?? 0 - rev.comb1Right[rev.posComb1 % rev.comb1Right.length] = - inputRight + c1R * feedback + const c1R = rev.comb1Right[rev.posComb1Right] ?? 0 + rev.comb1Right[rev.posComb1Right] = inputRight + c1R * feedback rev.posComb1 = (rev.posComb1 + 1) % rev.comb1Left.length + rev.posComb1Right = (rev.posComb1Right + 1) % rev.comb1Right.length const c2L = rev.comb2Left[rev.posComb2] ?? 0 rev.comb2Left[rev.posComb2] = inputLeft + c2L * feedback - const c2R = rev.comb2Right[rev.posComb2 % rev.comb2Right.length] ?? 0 - rev.comb2Right[rev.posComb2 % rev.comb2Right.length] = - inputRight + c2R * feedback + const c2R = rev.comb2Right[rev.posComb2Right] ?? 0 + rev.comb2Right[rev.posComb2Right] = inputRight + c2R * feedback rev.posComb2 = (rev.posComb2 + 1) % rev.comb2Left.length + rev.posComb2Right = (rev.posComb2Right + 1) % rev.comb2Right.length const combMixL = (c1L + c2L) * 0.5 const combMixR = (c1R + c2R) * 0.5 diff --git a/src/playback/processing/streamProcessor.ts b/src/playback/processing/streamProcessor.ts index 895ea032..680bf22e 100644 --- a/src/playback/processing/streamProcessor.ts +++ b/src/playback/processing/streamProcessor.ts @@ -1632,10 +1632,6 @@ class AACDecoderStream extends Transform { const frameInfo = this._findADTSFrame() if (!frameInfo) break - if (frameInfo.start > 0) { - this.ringBuffer.skip(frameInfo.start) - } - const adtsFrame = frameInfo.frame if (!this.isConfigured) { diff --git a/src/sources/gaana.ts b/src/sources/gaana.ts index 5f99dfc3..3ea855b0 100644 --- a/src/sources/gaana.ts +++ b/src/sources/gaana.ts @@ -27,7 +27,6 @@ const USER_AGENT = const API_URL = 'https://gaana.com/apiv2' const STREAM_URL_API = 'https://gaana.com/api/stream-url' const CRYPTO_KEY = Buffer.from('gy1t#b@jl(b$wtme', 'utf8') -const CRYPTO_IV = Buffer.from('xC4dmVJAq14BfntX', 'utf8') const HLS_BASE_URL = 'https://vodhlsgaana-ebw.akamaized.net/' /** @@ -826,9 +825,13 @@ export default class GaanaSource { const offset = Number.parseInt(encryptedData[0] || '', 10) if (Number.isNaN(offset)) return '' + const iv = Buffer.from( + encryptedData.substring(offset, offset + 16), + 'utf8' + ) const ciphertextB64 = encryptedData.substring(offset + 16) const ciphertext = Buffer.from(`${ciphertextB64}==`, 'base64') - const decipher = createDecipheriv('aes-128-cbc', CRYPTO_KEY, CRYPTO_IV) + const decipher = createDecipheriv('aes-128-cbc', CRYPTO_KEY, iv) decipher.setAutoPadding(false) let decrypted = decipher.update(ciphertext) diff --git a/src/sources/nicovideo.ts b/src/sources/nicovideo.ts index a0b08352..b9ea365c 100644 --- a/src/sources/nicovideo.ts +++ b/src/sources/nicovideo.ts @@ -520,21 +520,31 @@ export default class NicoVideoSource { } /** - * Converts the limited NicoVideo duration string into milliseconds. + * Converts a NicoVideo ISO-8601 duration string into milliseconds. * - * This preserves the source's current behavior, which only reads the final - * seconds segment from NicoVideo's duration string. + * Handles all duration components (hours, minutes and seconds), e.g. a + * "PT4M05S" string correctly resolves to 245000 ms instead of only the + * trailing seconds value. * * @param duration - NicoVideo ISO-8601 duration string. - * @returns Duration in milliseconds based on the parsed seconds component. + * @returns Duration in milliseconds. */ private parseDurationMillis(duration?: string): number { if (!duration) { return 0 } - const seconds = duration.match(/(\d+)S/)?.[1] - return Number.parseInt(seconds ?? '0', 10) * 1000 + const match = duration.match( + /PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+(?:\.\d+)?)S)?/ + ) + if (!match) { + return 0 + } + + const hours = Number.parseInt(match[1] ?? '0', 10) + const minutes = Number.parseInt(match[2] ?? '0', 10) + const seconds = Number.parseFloat(match[3] ?? '0') + return Math.round((hours * 3600 + minutes * 60 + seconds) * 1000) } /**