From d9f6f1666deab51a2fb1795808147b3c8636cdad Mon Sep 17 00:00:00 2001 From: killer069 <65385476+killer069@users.noreply.github.com> Date: Mon, 13 Dec 2021 12:30:54 +0530 Subject: [PATCH] Added seek mode option in stream --- play-dl/YouTube/classes/SeekStream.ts | 77 +++++++++++++++------------ play-dl/YouTube/classes/WebmSeeker.ts | 21 ++++++-- play-dl/YouTube/stream.ts | 1 + 3 files changed, 62 insertions(+), 37 deletions(-) diff --git a/play-dl/YouTube/classes/SeekStream.ts b/play-dl/YouTube/classes/SeekStream.ts index d4d094d..05ff753 100644 --- a/play-dl/YouTube/classes/SeekStream.ts +++ b/play-dl/YouTube/classes/SeekStream.ts @@ -1,5 +1,4 @@ import { IncomingMessage } from "http"; -import { Readable } from "stream"; import { request_stream } from "../../Request"; import { parseAudioFormats, StreamOptions, StreamType } from "../stream"; import { video_info } from "../utils"; @@ -7,11 +6,11 @@ import { Timer } from "./LiveStream"; import { WebmSeeker, WebmSeekerState } from "./WebmSeeker"; /** - * YouTube Stream Class for playing audio from normal videos. + * YouTube Stream Class for seeking audio to a timeStamp. */ export class SeekStream { /** - * Readable Stream through which data passes + * WebmSeeker Stream through which data passes */ stream: WebmSeeker; /** @@ -69,7 +68,7 @@ import { WebmSeeker, WebmSeekerState } from "./WebmSeeker"; video_url: string, options: StreamOptions ) { - this.stream = new WebmSeeker({ highWaterMark: 5 * 1000 * 1000, readableObjectMode : true }); + this.stream = new WebmSeeker({ highWaterMark: 5 * 1000 * 1000, readableObjectMode : true, mode : options.mode }); this.url = url; this.quality = options.quality as number; this.type = StreamType.Opus; @@ -88,44 +87,54 @@ import { WebmSeeker, WebmSeekerState } from "./WebmSeeker"; }); this.seek(options.seek!) } + /** + * **INTERNAL Function** + * + * Uses stream functions to parse Webm Head and gets Offset byte to seek to. + * @param sec No of seconds to seek to + * @returns Nothing + */ + private async seek(sec : number){ + await new Promise(async(res) => { + if(!this.stream.headerparsed){ + const stream = await request_stream(this.url, { + headers: { + range: `bytes=0-1000` + } + }).catch((err: Error) => err); - private seek(ms : number){ - return new Promise(async(res) => { - const stream = await request_stream(this.url, { - headers: { - range: `bytes=0-1000` - } - }).catch((err: Error) => err); - - if (stream instanceof Error) { - this.stream.emit('error', stream); - this.bytes_count = 0; - this.per_sec_bytes = 0; - this.cleanup(); - return; - } - - this.request = stream - stream.pipe(this.stream, { end : false }) - - stream.once('end', () => { - this.stream.state = WebmSeekerState.READING_DATA - - const bytes = this.stream.seek(ms) - if (bytes instanceof Error) { - this.stream.emit('error', bytes); + if (stream instanceof Error) { + this.stream.emit('error', stream); this.bytes_count = 0; this.per_sec_bytes = 0; this.cleanup(); return; } - this.bytes_count = bytes - this.timer.reuse() - this.loop() - res('') - }) + this.request = stream + stream.pipe(this.stream, { end : false }) + + stream.once('end', () => { + this.stream.state = WebmSeekerState.READING_DATA + res('') + }) + } + else res('') }) + + const bytes = this.stream.seek(sec) + if (bytes instanceof Error) { + this.stream.emit('error', bytes); + this.bytes_count = 0; + this.per_sec_bytes = 0; + this.cleanup(); + return; + } + + this.stream.seekfound = false + this.bytes_count = bytes + this.timer.reuse() + this.loop() } /** * Retry if we get 404 or 403 Errors. diff --git a/play-dl/YouTube/classes/WebmSeeker.ts b/play-dl/YouTube/classes/WebmSeeker.ts index 2324b5f..0956851 100644 --- a/play-dl/YouTube/classes/WebmSeeker.ts +++ b/play-dl/YouTube/classes/WebmSeeker.ts @@ -8,25 +8,35 @@ export enum WebmSeekerState{ READING_DATA = 'READING_DATA', } +interface WebmSeekerOptions extends DuplexOptions{ + mode? : "precise" | "granular" +} + export class WebmSeeker extends Duplex{ remaining? : Buffer state : WebmSeekerState + mode : "precise" | "granular" chunk? : Buffer cursor : number header : WebmHeader headfound : boolean + headerparsed : boolean + time_left : number seekfound : boolean private data_size : number private data_length : number - constructor(options? : DuplexOptions){ + constructor(options : WebmSeekerOptions){ super(options) this.state = WebmSeekerState.READING_HEAD this.cursor = 0 this.header = new WebmHeader() this.headfound = false + this.time_left = 0 + this.headerparsed = false this.seekfound = false this.data_length = 0 + this.mode = options.mode || "granular" this.data_size = 0 } @@ -58,9 +68,10 @@ export class WebmSeeker extends Duplex{ _read() {} - seek(ms : number): Error | number{ + seek(sec : number): Error | number{ let position = 0 - let time = (Math.floor(ms / 10) * 10) + let time = (Math.floor(sec / 10) * 10) + this.time_left = (sec - time) * 1000 || 0 if (!this.header.segment.cues) return new Error("Failed to Parse Cues") for(const data of this.header.segment.cues){ @@ -172,6 +183,10 @@ export class WebmSeeker extends Duplex{ else this.cursor += this.data_size + this.data_length if(ebmlID.name === 'simpleBlock'){ + if(this.time_left !== 0 && this.mode === "precise"){ + if(data.readUInt16BE(1) === this.time_left) this.time_left = 0 + else continue; + } const track = this.header.segment.tracks![this.header.audioTrack] if(!track || track.trackType !== 2) return new Error("No audio Track in this webm file.") if((data[0] & 0xf) === track.trackNumber) this.push(data.slice(4)) diff --git a/play-dl/YouTube/stream.ts b/play-dl/YouTube/stream.ts index 2aca551..531aa24 100644 --- a/play-dl/YouTube/stream.ts +++ b/play-dl/YouTube/stream.ts @@ -12,6 +12,7 @@ export enum StreamType { } export interface StreamOptions { + mode? : "precise" | "granular" seek? : number quality?: number; htmldata?: boolean;