Added seek mode option in stream
This commit is contained in:
parent
f2598c9a7c
commit
d9f6f1666d
@ -1,5 +1,4 @@
|
|||||||
import { IncomingMessage } from "http";
|
import { IncomingMessage } from "http";
|
||||||
import { Readable } from "stream";
|
|
||||||
import { request_stream } from "../../Request";
|
import { request_stream } from "../../Request";
|
||||||
import { parseAudioFormats, StreamOptions, StreamType } from "../stream";
|
import { parseAudioFormats, StreamOptions, StreamType } from "../stream";
|
||||||
import { video_info } from "../utils";
|
import { video_info } from "../utils";
|
||||||
@ -7,11 +6,11 @@ import { Timer } from "./LiveStream";
|
|||||||
import { WebmSeeker, WebmSeekerState } from "./WebmSeeker";
|
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 {
|
export class SeekStream {
|
||||||
/**
|
/**
|
||||||
* Readable Stream through which data passes
|
* WebmSeeker Stream through which data passes
|
||||||
*/
|
*/
|
||||||
stream: WebmSeeker;
|
stream: WebmSeeker;
|
||||||
/**
|
/**
|
||||||
@ -69,7 +68,7 @@ import { WebmSeeker, WebmSeekerState } from "./WebmSeeker";
|
|||||||
video_url: string,
|
video_url: string,
|
||||||
options: StreamOptions
|
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.url = url;
|
||||||
this.quality = options.quality as number;
|
this.quality = options.quality as number;
|
||||||
this.type = StreamType.Opus;
|
this.type = StreamType.Opus;
|
||||||
@ -88,44 +87,54 @@ import { WebmSeeker, WebmSeekerState } from "./WebmSeeker";
|
|||||||
});
|
});
|
||||||
this.seek(options.seek!)
|
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){
|
if (stream instanceof Error) {
|
||||||
return new Promise(async(res) => {
|
this.stream.emit('error', stream);
|
||||||
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);
|
|
||||||
this.bytes_count = 0;
|
this.bytes_count = 0;
|
||||||
this.per_sec_bytes = 0;
|
this.per_sec_bytes = 0;
|
||||||
this.cleanup();
|
this.cleanup();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.bytes_count = bytes
|
this.request = stream
|
||||||
this.timer.reuse()
|
stream.pipe(this.stream, { end : false })
|
||||||
this.loop()
|
|
||||||
res('')
|
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.
|
* Retry if we get 404 or 403 Errors.
|
||||||
|
|||||||
@ -8,25 +8,35 @@ export enum WebmSeekerState{
|
|||||||
READING_DATA = 'READING_DATA',
|
READING_DATA = 'READING_DATA',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface WebmSeekerOptions extends DuplexOptions{
|
||||||
|
mode? : "precise" | "granular"
|
||||||
|
}
|
||||||
|
|
||||||
export class WebmSeeker extends Duplex{
|
export class WebmSeeker extends Duplex{
|
||||||
remaining? : Buffer
|
remaining? : Buffer
|
||||||
state : WebmSeekerState
|
state : WebmSeekerState
|
||||||
|
mode : "precise" | "granular"
|
||||||
chunk? : Buffer
|
chunk? : Buffer
|
||||||
cursor : number
|
cursor : number
|
||||||
header : WebmHeader
|
header : WebmHeader
|
||||||
headfound : boolean
|
headfound : boolean
|
||||||
|
headerparsed : boolean
|
||||||
|
time_left : number
|
||||||
seekfound : boolean
|
seekfound : boolean
|
||||||
private data_size : number
|
private data_size : number
|
||||||
private data_length : number
|
private data_length : number
|
||||||
|
|
||||||
constructor(options? : DuplexOptions){
|
constructor(options : WebmSeekerOptions){
|
||||||
super(options)
|
super(options)
|
||||||
this.state = WebmSeekerState.READING_HEAD
|
this.state = WebmSeekerState.READING_HEAD
|
||||||
this.cursor = 0
|
this.cursor = 0
|
||||||
this.header = new WebmHeader()
|
this.header = new WebmHeader()
|
||||||
this.headfound = false
|
this.headfound = false
|
||||||
|
this.time_left = 0
|
||||||
|
this.headerparsed = false
|
||||||
this.seekfound = false
|
this.seekfound = false
|
||||||
this.data_length = 0
|
this.data_length = 0
|
||||||
|
this.mode = options.mode || "granular"
|
||||||
this.data_size = 0
|
this.data_size = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,9 +68,10 @@ export class WebmSeeker extends Duplex{
|
|||||||
|
|
||||||
_read() {}
|
_read() {}
|
||||||
|
|
||||||
seek(ms : number): Error | number{
|
seek(sec : number): Error | number{
|
||||||
let position = 0
|
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")
|
if (!this.header.segment.cues) return new Error("Failed to Parse Cues")
|
||||||
|
|
||||||
for(const data of this.header.segment.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
|
else this.cursor += this.data_size + this.data_length
|
||||||
|
|
||||||
if(ebmlID.name === 'simpleBlock'){
|
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]
|
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(!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))
|
if((data[0] & 0xf) === track.trackNumber) this.push(data.slice(4))
|
||||||
|
|||||||
@ -12,6 +12,7 @@ export enum StreamType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface StreamOptions {
|
export interface StreamOptions {
|
||||||
|
mode? : "precise" | "granular"
|
||||||
seek? : number
|
seek? : number
|
||||||
quality?: number;
|
quality?: number;
|
||||||
htmldata?: boolean;
|
htmldata?: boolean;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user