commit
8605d98059
@ -65,7 +65,6 @@ export class SeekStream {
|
|||||||
this.stream = new WebmSeeker({
|
this.stream = new WebmSeeker({
|
||||||
highWaterMark: 5 * 1000 * 1000,
|
highWaterMark: 5 * 1000 * 1000,
|
||||||
readableObjectMode: true,
|
readableObjectMode: true,
|
||||||
mode: options.seekMode
|
|
||||||
});
|
});
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.quality = options.quality as number;
|
this.quality = options.quality as number;
|
||||||
|
|||||||
@ -23,13 +23,11 @@ const WEB_ELEMENT_KEYS = Object.keys(WebmElements);
|
|||||||
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;
|
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;
|
||||||
@ -40,11 +38,9 @@ export class WebmSeeker extends Duplex {
|
|||||||
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.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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,21 +71,22 @@ export class WebmSeeker extends Duplex {
|
|||||||
|
|
||||||
_read() {}
|
_read() {}
|
||||||
|
|
||||||
seek(sec: number): Error | number {
|
seek(sec : number): Error | number{
|
||||||
let position = 0;
|
let clusterlength = 0, position = 0;
|
||||||
const time = Math.floor(sec / 10) * 10;
|
const time = Math.floor(sec / 10) * 10;
|
||||||
this.time_left = (sec - time) * 1000 || 0;
|
let time_left = (sec - time) * 1000 || 0;
|
||||||
this.time_left = Math.round(this.time_left / 20) * 20;
|
time_left = Math.round(time_left / 20) * 20;
|
||||||
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 (let i = 0; i < this.header.segment.cues.length; i++) {
|
||||||
|
const data = this.header.segment.cues[i]
|
||||||
if (Math.floor((data.time as number) / 1000) === time) {
|
if (Math.floor((data.time as number) / 1000) === time) {
|
||||||
position = data.position as number;
|
position = data.position as number;
|
||||||
|
clusterlength = this.header.segment.cues[i + 1].position! - position - 1
|
||||||
break;
|
break;
|
||||||
} else continue;
|
} else continue;
|
||||||
}
|
}
|
||||||
if (position === 0) return new Error('Failed to find Cluster Position');
|
return Math.round(position + (time_left / 20) * (clusterlength / 500));
|
||||||
else return position;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_write(chunk: Buffer, _: BufferEncoding, callback: (error?: Error | null) => void): void {
|
_write(chunk: Buffer, _: BufferEncoding, callback: (error?: Error | null) => void): void {
|
||||||
@ -101,7 +98,7 @@ export class WebmSeeker extends Duplex {
|
|||||||
let err: Error | undefined;
|
let err: Error | undefined;
|
||||||
|
|
||||||
if (this.state === WebmSeekerState.READING_HEAD) err = this.readHead();
|
if (this.state === WebmSeekerState.READING_HEAD) err = this.readHead();
|
||||||
else if (!this.seekfound) err = this.getClosestCluster();
|
else if (!this.seekfound) err = this.getClosestBlock();
|
||||||
else err = this.readTag();
|
else err = this.readTag();
|
||||||
|
|
||||||
if (err) callback(err);
|
if (err) callback(err);
|
||||||
@ -196,10 +193,6 @@ 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));
|
||||||
@ -209,11 +202,28 @@ export class WebmSeeker extends Duplex {
|
|||||||
this.cursor = 0;
|
this.cursor = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getClosestCluster(): Error | undefined {
|
private getClosestBlock(): Error | undefined {
|
||||||
if (!this.chunk) return new Error('Chunk is missing');
|
if (!this.chunk) return new Error('Chunk is missing');
|
||||||
const count = this.chunk.indexOf('1f43b675', 0, 'hex');
|
this.cursor = 0
|
||||||
if (count === -1) throw new Error('Failed to find nearest Cluster.');
|
let positionFound = false;
|
||||||
else this.chunk = this.chunk.slice(count);
|
while(!positionFound){
|
||||||
|
this.cursor = this.chunk.indexOf('a3', this.cursor, 'hex');
|
||||||
|
if (this.cursor === -1) return new Error('Failed to find nearest Block.');
|
||||||
|
this.cursor ++;
|
||||||
|
if(!this.vint_value()) return new Error("Failed to find correct simpleBlock in first chunk")
|
||||||
|
const data = this.chunk.slice(
|
||||||
|
this.cursor + this.data_size,
|
||||||
|
this.cursor + this.data_size + this.data_length
|
||||||
|
);
|
||||||
|
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.cursor += this.data_size + this.data_length;
|
||||||
|
this.push(data.slice(4))
|
||||||
|
positionFound = true;
|
||||||
|
}
|
||||||
|
else continue;
|
||||||
|
}
|
||||||
this.seekfound = true;
|
this.seekfound = true;
|
||||||
return this.readTag();
|
return this.readTag();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,7 +14,6 @@ export enum StreamType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface StreamOptions {
|
export interface StreamOptions {
|
||||||
seekMode?: 'precise' | 'granular';
|
|
||||||
seek?: number;
|
seek?: number;
|
||||||
quality?: number;
|
quality?: number;
|
||||||
language?: string;
|
language?: string;
|
||||||
|
|||||||
@ -77,7 +77,7 @@ import { EventEmitter } from 'stream';
|
|||||||
|
|
||||||
async function stream(
|
async function stream(
|
||||||
url: string,
|
url: string,
|
||||||
options: { seek?: number; seekMode?: 'precise' | 'granular' } & StreamOptions
|
options: { seek?: number } & StreamOptions
|
||||||
): Promise<YouTubeStream>;
|
): Promise<YouTubeStream>;
|
||||||
async function stream(url: string, options?: StreamOptions): Promise<YouTubeStream | SoundCloudStream>;
|
async function stream(url: string, options?: StreamOptions): Promise<YouTubeStream | SoundCloudStream>;
|
||||||
/**
|
/**
|
||||||
@ -89,13 +89,8 @@ async function stream(url: string, options?: StreamOptions): Promise<YouTubeStre
|
|||||||
*
|
*
|
||||||
* const source = await play.stream('soundcloud track URL') // SoundCloud Track Stream
|
* const source = await play.stream('soundcloud track URL') // SoundCloud Track Stream
|
||||||
*
|
*
|
||||||
* const source = await play.stream('youtube video URL', { seek : 45 }) // Seeks 45 seconds (approx.) in YouTube Video Stream [ seekMode = "granular" ]
|
* const source = await play.stream('youtube video URL', { seek : 45 }) // Seeks 45 seconds (approx.) in YouTube Video Stream
|
||||||
* // Granular = 1 - 9 seconds before given time. [ Fast ]
|
*
|
||||||
* // Precise = Exact seek [ Little bit Slow ]
|
|
||||||
* // Above command seeks to 45 seconds approximately while below command seeks to 45 seconds precisely
|
|
||||||
*
|
|
||||||
* const source = await play.stream('youtube video URL', { seek : 45, seekMode: "precise" }) // Seeks precisely to 45 seconds in YouTube Video Stream
|
|
||||||
*
|
|
||||||
* const resource = createAudioResource(source.stream, {
|
* const resource = createAudioResource(source.stream, {
|
||||||
* inputType : source.type
|
* inputType : source.type
|
||||||
* }) // Use discordjs voice createAudioResource function.
|
* }) // Use discordjs voice createAudioResource function.
|
||||||
@ -104,7 +99,6 @@ async function stream(url: string, options?: StreamOptions): Promise<YouTubeStre
|
|||||||
* @param options
|
* @param options
|
||||||
*
|
*
|
||||||
* - `number` seek : No of seconds to seek in stream.
|
* - `number` seek : No of seconds to seek in stream.
|
||||||
* - `string` seekMode : Choose type of seek you want. [ "precise" | "granular" ]
|
|
||||||
* - `string` language : Sets language of searched content [ YouTube search only. ], e.g. "en-US"
|
* - `string` language : Sets language of searched content [ YouTube search only. ], e.g. "en-US"
|
||||||
* - `number` quality : Quality number. [ 0 = Lowest, 1 = Medium, 2 = Highest ]
|
* - `number` quality : Quality number. [ 0 = Lowest, 1 = Medium, 2 = Highest ]
|
||||||
* - `boolean` htmldata : given data is html data or not
|
* - `boolean` htmldata : given data is html data or not
|
||||||
@ -234,12 +228,7 @@ async function stream_from_info(info: InfoData, options?: StreamOptions): Promis
|
|||||||
* const soundInfo = await play.soundcloud('SoundCloud URL')
|
* const soundInfo = await play.soundcloud('SoundCloud URL')
|
||||||
* const source = await play.stream_from_info(soundInfo) // SoundCloud Track Stream
|
* const source = await play.stream_from_info(soundInfo) // SoundCloud Track Stream
|
||||||
*
|
*
|
||||||
* const source = await play.stream_from_info(info, { seek : 45 }) // Seeks 45 seconds (approx.) in YouTube Video Stream [ seekMode = "granular" ]
|
* const source = await play.stream_from_info(info, { seek : 45 }) // Seeks 45 seconds (approx.) in YouTube Video Stream
|
||||||
* // Granular = 1 - 9 seconds before given time. [ Fast ]
|
|
||||||
* // Precise = Exact seek [ Little bit Slow ]
|
|
||||||
* // Above command seeks to 45 seconds approximately while below command seeks to 45 seconds precisely
|
|
||||||
*
|
|
||||||
* const source = await play.stream_from_info(info, { seek : 45, seekMode: "precise" }) // Seeks precisely to 45 seconds in YouTube Video Stream
|
|
||||||
*
|
*
|
||||||
* const resource = createAudioResource(source.stream, {
|
* const resource = createAudioResource(source.stream, {
|
||||||
* inputType : source.type
|
* inputType : source.type
|
||||||
@ -249,7 +238,6 @@ async function stream_from_info(info: InfoData, options?: StreamOptions): Promis
|
|||||||
* @param options
|
* @param options
|
||||||
*
|
*
|
||||||
* - `number` seek : No of seconds to seek in stream.
|
* - `number` seek : No of seconds to seek in stream.
|
||||||
* - `string` seekMode : Choose type of seek you want. [ "precise" | "granular" ]
|
|
||||||
* - `string` language : Sets language of searched content [ YouTube search only. ], e.g. "en-US"
|
* - `string` language : Sets language of searched content [ YouTube search only. ], e.g. "en-US"
|
||||||
* - `number` quality : Quality number. [ 0 = Lowest, 1 = Medium, 2 = Highest ]
|
* - `number` quality : Quality number. [ 0 = Lowest, 1 = Medium, 2 = Highest ]
|
||||||
* - `boolean` htmldata : given data is html data or not
|
* - `boolean` htmldata : given data is html data or not
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user