From 79419d22058e0c51e009559e5bcba78a19e1cbf0 Mon Sep 17 00:00:00 2001 From: killer069 <65385476+killer069@users.noreply.github.com> Date: Tue, 5 Oct 2021 18:47:09 +0530 Subject: [PATCH] Stream and Validate fixes --- play-dl/SoundCloud/classes.ts | 43 ++++----- play-dl/SoundCloud/index.ts | 3 +- play-dl/YouTube/classes/LiveStream.ts | 124 +++++++++++++++++++------- play-dl/YouTube/utils/extractor.ts | 6 +- play-dl/index.ts | 20 +++++ 5 files changed, 137 insertions(+), 59 deletions(-) diff --git a/play-dl/SoundCloud/classes.ts b/play-dl/SoundCloud/classes.ts index 9674af3..a854172 100644 --- a/play-dl/SoundCloud/classes.ts +++ b/play-dl/SoundCloud/classes.ts @@ -2,6 +2,7 @@ import { request, request_stream } from '../YouTube/utils/request'; import { PassThrough } from 'stream'; import { IncomingMessage } from 'http'; import { StreamType } from '../YouTube/stream'; +import { Timer } from '../YouTube/classes/LiveStream'; interface SoundCloudUser { name: string; @@ -204,37 +205,28 @@ export class Stream { stream: PassThrough; type: StreamType; private url: string; - private playing_count: number; private downloaded_time: number; + private timer: Timer; private downloaded_segments: number; private request: IncomingMessage | null; - private data_ended: boolean; private time: number[]; private segment_urls: string[]; constructor(url: string, type: StreamType = StreamType.Arbitrary) { this.stream = new PassThrough({ highWaterMark: 10 * 1000 * 1000 }); this.type = type; this.url = url; - this.playing_count = 0; this.downloaded_time = 0; this.request = null; this.downloaded_segments = 0; - this.data_ended = false; this.time = []; + this.timer = new Timer(() => { + this.timer.reuse(); + this.start(); + }, 280); this.segment_urls = []; this.stream.on('close', () => { this.cleanup(); }); - this.stream.on('pause', () => { - this.playing_count++; - if (this.data_ended) { - this.cleanup(); - this.stream.removeAllListeners('pause'); - } else if (this.playing_count === 110) { - this.playing_count = 0; - this.start(); - } - }); this.start(); } @@ -268,19 +260,20 @@ export class Stream { } private async loop() { - if (this.stream.destroyed) { + if (this.stream.destroyed ||this.time.length === 0 || this.segment_urls.length === 0) { this.cleanup(); return; } - if (this.time.length === 0 || this.segment_urls.length === 0) { - this.data_ended = true; - return; - } this.downloaded_time += this.time.shift() as number; this.downloaded_segments++; const stream = await request_stream(this.segment_urls.shift() as string).catch((err: Error) => err); - if (stream instanceof Error) throw stream; + if (stream instanceof Error) { + this.stream.emit('error', stream); + this.cleanup(); + return; + } + this.request = stream stream.pipe(this.stream, { end: false }); stream.on('end', () => { if (this.downloaded_time >= 300) return; @@ -292,14 +285,22 @@ export class Stream { } private cleanup() { + this.timer.destroy(); this.request?.unpipe(this.stream); this.request?.destroy(); this.url = ''; - this.playing_count = 0; this.downloaded_time = 0; this.downloaded_segments = 0; this.request = null; this.time = []; this.segment_urls = []; } + + pause() { + this.timer.pause(); + } + + resume() { + this.timer.resume(); + } } diff --git a/play-dl/SoundCloud/index.ts b/play-dl/SoundCloud/index.ts index 7c59028..5f8a56e 100644 --- a/play-dl/SoundCloud/index.ts +++ b/play-dl/SoundCloud/index.ts @@ -127,11 +127,12 @@ export async function check_id(id: string): Promise { * @returns "false" | 'track' | 'playlist' */ export async function so_validate(url: string): Promise { + if (!url.match(pattern)) return false; const data = await request( `https://api-v2.soundcloud.com/resolve?url=${url}&client_id=${soundData.client_id}` ).catch((err: Error) => err); - if (data instanceof Error) throw data; + if (data instanceof Error) return false; const json_data = JSON.parse(data); if (json_data.kind === 'track') return 'track'; diff --git a/play-dl/YouTube/classes/LiveStream.ts b/play-dl/YouTube/classes/LiveStream.ts index 8b3d2af..9b2d20f 100644 --- a/play-dl/YouTube/classes/LiveStream.ts +++ b/play-dl/YouTube/classes/LiveStream.ts @@ -17,9 +17,9 @@ export class LiveStreaming { private url: string; private interval: number; private packet_count: number; - private timer: NodeJS.Timer | null; + private timer: Timer; private video_url: string; - private dash_timer: NodeJS.Timer | null; + private dash_timer: Timer; private segments_urls: string[]; private request: IncomingMessage | null; constructor(dash_url: string, target_interval: number, video_url: string) { @@ -30,12 +30,15 @@ export class LiveStreaming { this.segments_urls = []; this.packet_count = 0; this.request = null; - this.timer = null; this.video_url = video_url; - this.interval = target_interval * 1000 || 0; - this.dash_timer = setTimeout(() => { + this.interval = target_interval || 0; + this.timer = new Timer(() => { + this.start(); + }, this.interval); + this.dash_timer = new Timer(() => { + this.dash_timer.reuse(); this.dash_updater(); - }, 1800000); + }, 1800); this.stream.on('close', () => { this.cleanup(); }); @@ -51,9 +54,6 @@ export class LiveStreaming { ) { this.url = info.LiveStreamData.dashManifestUrl; } - this.dash_timer = setTimeout(() => { - this.dash_updater(); - }, 1800000); } private async dash_getter() { @@ -70,14 +70,12 @@ export class LiveStreaming { } private cleanup() { - clearTimeout(this.timer as NodeJS.Timer); - clearTimeout(this.dash_timer as NodeJS.Timer); + this.timer.destroy(); + this.dash_timer.destroy(); this.request?.unpipe(this.stream); this.request?.destroy(); - this.dash_timer = null; this.video_url = ''; this.request = null; - this.timer = null; this.url = ''; this.base_url = ''; this.segments_urls = []; @@ -114,10 +112,12 @@ export class LiveStreaming { }); }); } - this.timer = setTimeout(() => { - this.start(); - }, this.interval); + + this.timer.reuse(); } + + pause() {} + resume() {} } /** * Class for YouTube Stream @@ -131,8 +131,7 @@ export class Stream { private content_length: number; private video_url: string; private cookie: string; - private data_ended: boolean; - private playing_count: number; + private timer: Timer; private quality: number; private proxy: Proxy[] | undefined; private request: IncomingMessage | null; @@ -156,23 +155,13 @@ export class Stream { this.per_sec_bytes = Math.ceil(contentLength / duration); this.content_length = contentLength; this.request = null; - this.data_ended = false; - this.playing_count = 0; + this.timer = new Timer(() => { + this.timer.reuse(); + this.loop(); + }, 280); this.stream.on('close', () => { this.cleanup(); }); - this.stream.on('pause', () => { - this.playing_count++; - if (this.data_ended) { - this.bytes_count = 0; - this.per_sec_bytes = 0; - this.cleanup(); - this.stream.removeAllListeners('pause'); - } else if (this.playing_count === 280) { - this.playing_count = 0; - this.loop(); - } - }); this.loop(); } @@ -202,7 +191,6 @@ export class Stream { }).catch((err: Error) => err); if (stream instanceof Error) { this.stream.emit('error', stream); - this.data_ended = true; this.bytes_count = 0; this.per_sec_bytes = 0; this.cleanup(); @@ -228,7 +216,75 @@ export class Stream { }); stream.on('end', () => { - if (end >= this.content_length) this.data_ended = true; + if (end >= this.content_length) { + this.timer.destroy(); + } }); } + + pause() { + this.timer.pause(); + } + + resume() { + this.timer.resume(); + } +} + +export class Timer { + private destroyed: boolean; + private paused: boolean; + private timer: NodeJS.Timer; + private callback: () => void; + private time_start: number; + private time_left: number; + private time_total: number; + constructor(callback: () => void, time: number) { + this.callback = callback; + this.time_total = time; + this.time_left = time; + this.paused = false; + this.destroyed = false; + this.time_start = process.hrtime()[0]; + this.timer = setTimeout(this.callback, this.time_total * 1000); + } + + pause() { + if (!this.paused && !this.destroyed) { + this.paused = true; + clearTimeout(this.timer); + this.time_left = this.time_left - (process.hrtime()[0] - this.time_start); + return true; + } else return false; + } + + resume() { + if (this.paused && !this.destroyed) { + this.paused = false; + this.time_start = process.hrtime()[0]; + this.timer = setTimeout(this.callback, this.time_left * 1000); + return true; + } else return false; + } + + reuse() { + if (!this.destroyed) { + clearTimeout(this.timer); + this.time_left = this.time_total; + this.paused = false; + this.time_start = process.hrtime()[0]; + this.timer = setTimeout(this.callback, this.time_total * 1000); + return true; + } else return false; + } + + destroy() { + clearTimeout(this.timer); + this.destroyed = true; + this.callback = () => {}; + this.time_total = 0; + this.time_left = 0; + this.paused = false; + this.time_start = 0; + } } diff --git a/play-dl/YouTube/utils/extractor.ts b/play-dl/YouTube/utils/extractor.ts index 666bb41..e30395e 100644 --- a/play-dl/YouTube/utils/extractor.ts +++ b/play-dl/YouTube/utils/extractor.ts @@ -41,7 +41,7 @@ export function yt_validate(url: string): 'playlist' | 'video' | false { if (Playlist_id.length !== 34 || !Playlist_id.startsWith('PL')) { return false; } - return 'playlist'; + else return 'playlist'; } } /** @@ -115,8 +115,8 @@ export async function video_basic_info(url: string, options: InfoOptions = {}) { url: `https://www.youtube.com/watch?v=${vid.videoId}`, title: vid.title, description: vid.shortDescription, - duration: vid.lengthSeconds, - duration_raw: parseSeconds(vid.lengthSeconds), + duration: Number(vid.lengthSeconds), + duration_raw: parseSeconds(Number(vid.lengthSeconds)), uploadedAt: microformat.publishDate, thumbnail: vid.thumbnail.thumbnails[vid.thumbnail.thumbnails.length - 1], channel: { diff --git a/play-dl/index.ts b/play-dl/index.ts index b4ae6a5..85d3e47 100644 --- a/play-dl/index.ts +++ b/play-dl/index.ts @@ -2,6 +2,14 @@ export { playlist_info, video_basic_info, video_info, yt_validate, extractID, Yo export { spotify, sp_validate, refreshToken, is_expired, Spotify } from './Spotify'; export { soundcloud, so_validate, SoundCloud, SoundCloudStream } from './SoundCloud'; +enum AudioPlayerStatus { + Idle = 'idle', + Buffering = 'buffering', + Paused = 'paused', + Playing = 'playing', + AutoPaused = 'autopaused' +} + interface SearchOptions { limit?: number; source?: { @@ -28,6 +36,7 @@ import { check_id, so_search, stream as so_stream, stream_from_info as so_stream import { InfoData, stream as yt_stream, StreamOptions, stream_from_info as yt_stream_info } from './YouTube/stream'; import { SoundCloudTrack } from './SoundCloud/classes'; import { yt_search } from './YouTube/search'; +import { EventEmitter } from 'stream'; /** * Main stream Command for streaming through various sources @@ -170,3 +179,14 @@ export function authorization(): void { } }); } + +export function attachListeners(player: EventEmitter, resource: YouTubeStream | SoundCloudStream) { + player.on(AudioPlayerStatus.Paused, () => resource.pause()); + player.on(AudioPlayerStatus.AutoPaused, () => resource.pause()); + player.on(AudioPlayerStatus.Playing, () => resource.resume()); + player.once(AudioPlayerStatus.Idle, () => { + player.removeListener(AudioPlayerStatus.Paused, () => resource.pause()); + player.removeListener(AudioPlayerStatus.AutoPaused, () => resource.pause()); + player.removeListener(AudioPlayerStatus.Playing, () => resource.resume()); + }); +}