From 433ec4976b66cc01bf9a7acb5c05abd64edade9d Mon Sep 17 00:00:00 2001 From: killer069 <65385476+killer069@users.noreply.github.com> Date: Mon, 3 Jan 2022 15:02:52 +0530 Subject: [PATCH 1/5] Added property to get all tracks in playlist, album --- play-dl/Deezer/classes.ts | 30 ++++++++++++ play-dl/SoundCloud/classes.ts | 27 +++++------ play-dl/Spotify/classes.ts | 75 +++++++++++++++++------------ play-dl/YouTube/classes/Playlist.ts | 10 ++-- 4 files changed, 93 insertions(+), 49 deletions(-) diff --git a/play-dl/Deezer/classes.ts b/play-dl/Deezer/classes.ts index 959b5e5..5fa85c0 100644 --- a/play-dl/Deezer/classes.ts +++ b/play-dl/Deezer/classes.ts @@ -491,6 +491,21 @@ export class DeezerAlbum { return this; } + /** + * Fetches all the tracks in the album and returns them + * + * ```ts + * const album = await play.deezer('album url') + * + * const tracks = await album.all_tracks() + * ``` + * @returns An array of {@link DeezerTrack} + */ + async all_tracks(): Promise { + await this.fetch() + + return this.tracks as DeezerTrack[] + } /** * Converts instances of this class to JSON data * @returns JSON data. @@ -746,6 +761,21 @@ export class DeezerPlaylist { return this; } + /** + * Fetches all the tracks in the playlist and returns them + * + * ```ts + * const playlist = await play.deezer('playlist url') + * + * const tracks = await playlist.all_tracks() + * ``` + * @returns An array of {@link DeezerTrack} + */ + async all_tracks(): Promise { + await this.fetch() + + return this.tracks as DeezerTrack[] + } /** * Converts instances of this class to JSON data * @returns JSON data. diff --git a/play-dl/SoundCloud/classes.ts b/play-dl/SoundCloud/classes.ts index 958378b..ec92cf0 100644 --- a/play-dl/SoundCloud/classes.ts +++ b/play-dl/SoundCloud/classes.ts @@ -314,6 +314,7 @@ export class SoundCloudPlaylist { } /** * Get total no. of fetched tracks + * @see {@link SoundCloudPlaylist.all_tracks} */ get total_tracks(): number { let count = 0; @@ -324,25 +325,19 @@ export class SoundCloudPlaylist { return count; } /** - * Get all fetched tracks as a array. - * - * For getting all feetched tracks - * + * Fetches all the tracks in the playlist and returns them + * * ```ts - * const playlist = await play.soundcloud("playlist url") - * - * await playlist.fetch() - * - * const result = playlist.fetched_tracks + * const playlist = await play.soundcloud('playlist url') + * + * const tracks = await playlist.all_tracks() * ``` + * @returns An array of {@link SoundCloudTrack} */ - get fetched_tracks(): SoundCloudTrack[] { - let result: SoundCloudTrack[] = []; - this.tracks.forEach((track) => { - if (track instanceof SoundCloudTrack) result.push(track); - else return; - }); - return result; + async all_tracks(): Promise { + await this.fetch() + + return this.tracks as SoundCloudTrack[] } /** * Converts Class to JSON data diff --git a/play-dl/Spotify/classes.ts b/play-dl/Spotify/classes.ts index 6f03dd4..79a7b9a 100644 --- a/play-dl/Spotify/classes.ts +++ b/play-dl/Spotify/classes.ts @@ -309,21 +309,8 @@ export class SpotifyPlaylist { return this.fetched_tracks.get(`${num}`) as SpotifyTrack[]; } /** - * Spotify Playlist total no of pages in a playlist - * - * For getting all songs in a playlist, - * - * ```ts - * const playlist = await play.spotify('playlist url') - * - * await playlist.fetch() - * - * const result = [] - * - * for (let i = 0; i <= playlist.tota_pages; i++) { - * result.push(playlist.page(i)) - * } - * ``` + * Gets total number of pages in that playlist class. + * @see {@link SpotifyPlaylist.all_tracks} */ get total_pages() { return this.fetched_tracks.size; @@ -336,6 +323,25 @@ export class SpotifyPlaylist { const page_number: number = this.total_pages; return (page_number - 1) * 100 + (this.fetched_tracks.get(`${page_number}`) as SpotifyTrack[]).length; } + /** + * Fetches all the tracks in the playlist and returns them + * + * ```ts + * const playlist = await play.spotify('playlist url') + * + * const tracks = await playlist.all_tracks() + * ``` + * @returns An array of {@link SpotifyTrack} + */ + async all_tracks() : Promise{ + await this.fetch() + + const tracks : SpotifyTrack[] = [] + + for(const page of this.fetched_tracks.values()) tracks.push(...page); + + return tracks + } /** * Converts Class to JSON * @returns JSON data @@ -508,21 +514,8 @@ export class SpotifyAlbum { return this.fetched_tracks.get(`${num}`); } /** - * Spotify Album total no of pages in a album - * - * For getting all songs in a album, - * - * ```ts - * const album = await play.spotify('album url') - * - * await album.fetch() - * - * const result = [] - * - * for (let i = 0; i <= album.tota_pages; i++) { - * result.push(album.page(i)) - * } - * ``` + * Gets total number of pages in that album class. + * @see {@link SpotifyAlbum.all_tracks} */ get total_pages() { return this.fetched_tracks.size; @@ -535,7 +528,29 @@ export class SpotifyAlbum { const page_number: number = this.total_pages; return (page_number - 1) * 100 + (this.fetched_tracks.get(`${page_number}`) as SpotifyTrack[]).length; } + /** + * Fetches all the tracks in the album and returns them + * + * ```ts + * const album = await play.spotify('album url') + * + * const tracks = await album.all_tracks() + * ``` + * @returns An array of {@link SpotifyTrack} + */ + async all_tracks() : Promise{ + await this.fetch() + const tracks : SpotifyTrack[] = [] + + for( const page of this.fetched_tracks.values() ) tracks.push(...page); + + return tracks + } + /** + * Converts Class to JSON + * @returns JSON data + */ toJSON(): AlbumJSON { return { name: this.name, diff --git a/play-dl/YouTube/classes/Playlist.ts b/play-dl/YouTube/classes/Playlist.ts index 6dcda34..00c171c 100644 --- a/play-dl/YouTube/classes/Playlist.ts +++ b/play-dl/YouTube/classes/Playlist.ts @@ -218,6 +218,12 @@ export class YouTubePlayList { } /** * Fetches all the videos in the playlist and returns them + * + * ```ts + * const playlist = await play.playlist_info('playlist url') + * + * const videos = await playlist.all_videos() + * ``` * @returns An array of {@link YouTubeVideo} objects * @see {@link YouTubePlayList.fetch} */ @@ -226,9 +232,7 @@ export class YouTubePlayList { const videos = []; - for (const [, page] of this.fetched_videos.entries()) { - videos.push(...page); - } + for (const page of this.fetched_videos.values()) videos.push(...page); return videos; } From ffbe88203729ebcd6eac16ba1b7fb93a7d68da4e Mon Sep 17 00:00:00 2001 From: killer069 <65385476+killer069@users.noreply.github.com> Date: Mon, 3 Jan 2022 15:59:32 +0530 Subject: [PATCH 2/5] Pretty Code + Music property added in YouTube Video --- play-dl/Deezer/classes.ts | 20 ++++++++++---------- play-dl/SoundCloud/classes.ts | 8 ++++---- play-dl/Spotify/classes.ts | 28 ++++++++++++++-------------- play-dl/YouTube/classes/Playlist.ts | 4 ++-- play-dl/YouTube/classes/Video.ts | 22 ++++++++++++++++++++-- play-dl/YouTube/index.ts | 1 + play-dl/YouTube/stream.ts | 2 +- play-dl/YouTube/utils/extractor.ts | 22 +++++++++++++++++++++- play-dl/index.ts | 10 +++++----- 9 files changed, 78 insertions(+), 39 deletions(-) diff --git a/play-dl/Deezer/classes.ts b/play-dl/Deezer/classes.ts index 5fa85c0..62022ad 100644 --- a/play-dl/Deezer/classes.ts +++ b/play-dl/Deezer/classes.ts @@ -493,18 +493,18 @@ export class DeezerAlbum { } /** * Fetches all the tracks in the album and returns them - * + * * ```ts * const album = await play.deezer('album url') - * + * * const tracks = await album.all_tracks() * ``` * @returns An array of {@link DeezerTrack} */ - async all_tracks(): Promise { - await this.fetch() + async all_tracks(): Promise { + await this.fetch(); - return this.tracks as DeezerTrack[] + return this.tracks as DeezerTrack[]; } /** * Converts instances of this class to JSON data @@ -763,18 +763,18 @@ export class DeezerPlaylist { } /** * Fetches all the tracks in the playlist and returns them - * + * * ```ts * const playlist = await play.deezer('playlist url') - * + * * const tracks = await playlist.all_tracks() * ``` * @returns An array of {@link DeezerTrack} */ - async all_tracks(): Promise { - await this.fetch() + async all_tracks(): Promise { + await this.fetch(); - return this.tracks as DeezerTrack[] + return this.tracks as DeezerTrack[]; } /** * Converts instances of this class to JSON data diff --git a/play-dl/SoundCloud/classes.ts b/play-dl/SoundCloud/classes.ts index ec92cf0..0fcff6e 100644 --- a/play-dl/SoundCloud/classes.ts +++ b/play-dl/SoundCloud/classes.ts @@ -326,18 +326,18 @@ export class SoundCloudPlaylist { } /** * Fetches all the tracks in the playlist and returns them - * + * * ```ts * const playlist = await play.soundcloud('playlist url') - * + * * const tracks = await playlist.all_tracks() * ``` * @returns An array of {@link SoundCloudTrack} */ async all_tracks(): Promise { - await this.fetch() + await this.fetch(); - return this.tracks as SoundCloudTrack[] + return this.tracks as SoundCloudTrack[]; } /** * Converts Class to JSON data diff --git a/play-dl/Spotify/classes.ts b/play-dl/Spotify/classes.ts index 79a7b9a..2c50259 100644 --- a/play-dl/Spotify/classes.ts +++ b/play-dl/Spotify/classes.ts @@ -325,22 +325,22 @@ export class SpotifyPlaylist { } /** * Fetches all the tracks in the playlist and returns them - * + * * ```ts * const playlist = await play.spotify('playlist url') - * + * * const tracks = await playlist.all_tracks() * ``` * @returns An array of {@link SpotifyTrack} */ - async all_tracks() : Promise{ - await this.fetch() + async all_tracks(): Promise { + await this.fetch(); - const tracks : SpotifyTrack[] = [] + const tracks: SpotifyTrack[] = []; - for(const page of this.fetched_tracks.values()) tracks.push(...page); + for (const page of this.fetched_tracks.values()) tracks.push(...page); - return tracks + return tracks; } /** * Converts Class to JSON @@ -530,22 +530,22 @@ export class SpotifyAlbum { } /** * Fetches all the tracks in the album and returns them - * + * * ```ts * const album = await play.spotify('album url') - * + * * const tracks = await album.all_tracks() * ``` * @returns An array of {@link SpotifyTrack} */ - async all_tracks() : Promise{ - await this.fetch() + async all_tracks(): Promise { + await this.fetch(); - const tracks : SpotifyTrack[] = [] + const tracks: SpotifyTrack[] = []; - for( const page of this.fetched_tracks.values() ) tracks.push(...page); + for (const page of this.fetched_tracks.values()) tracks.push(...page); - return tracks + return tracks; } /** * Converts Class to JSON diff --git a/play-dl/YouTube/classes/Playlist.ts b/play-dl/YouTube/classes/Playlist.ts index 00c171c..45d714b 100644 --- a/play-dl/YouTube/classes/Playlist.ts +++ b/play-dl/YouTube/classes/Playlist.ts @@ -218,10 +218,10 @@ export class YouTubePlayList { } /** * Fetches all the videos in the playlist and returns them - * + * * ```ts * const playlist = await play.playlist_info('playlist url') - * + * * const videos = await playlist.all_videos() * ``` * @returns An array of {@link YouTubeVideo} objects diff --git a/play-dl/YouTube/classes/Video.ts b/play-dl/YouTube/classes/Video.ts index 6dcf2c3..63f8587 100644 --- a/play-dl/YouTube/classes/Video.ts +++ b/play-dl/YouTube/classes/Video.ts @@ -1,6 +1,14 @@ import { YouTubeChannel } from './Channel'; import { YouTubeThumbnail } from './Thumbnail'; +interface VideoMusic { + song?: string; + artist?: string; + album?: string; + writers?: string; + license?: string; +} + interface VideoOptions { /** * YouTube Video ID @@ -66,6 +74,10 @@ interface VideoOptions { * `true` if the video has been identified by the YouTube community as inappropriate or offensive to some audiences and viewer discretion is advised */ discretionAdvised?: boolean; + /** + * Gives info about music content in that video. + */ + music?: VideoMusic[]; } /** * Class for YouTube Video url @@ -135,6 +147,10 @@ export class YouTubeVideo { * `true` if the video has been identified by the YouTube community as inappropriate or offensive to some audiences and viewer discretion is advised */ discretionAdvised?: boolean; + /** + * Gives info about music content in that video. + */ + music?: VideoMusic[]; /** * Constructor for YouTube Video Class * @param data JSON parsed data. @@ -161,7 +177,8 @@ export class YouTubeVideo { this.live = !!data.live; this.private = !!data.private; this.tags = data.tags || []; - this.discretionAdvised = data.discretionAdvised === undefined ? undefined : data.discretionAdvised; + this.discretionAdvised = data.discretionAdvised ?? undefined; + this.music = data.music || []; } /** * Converts class to title name of video. @@ -190,7 +207,8 @@ export class YouTubeVideo { likes: this.likes, live: this.live, private: this.private, - discretionAdvised: this.discretionAdvised + discretionAdvised: this.discretionAdvised, + music: this.music }; } } diff --git a/play-dl/YouTube/index.ts b/play-dl/YouTube/index.ts index d9c6850..b9f6ac6 100644 --- a/play-dl/YouTube/index.ts +++ b/play-dl/YouTube/index.ts @@ -4,3 +4,4 @@ export { YouTube } from './search'; export { YouTubeVideo } from './classes/Video'; export { YouTubePlayList } from './classes/Playlist'; export { YouTubeChannel } from './classes/Channel'; +export { InfoData } from './utils/constants'; diff --git a/play-dl/YouTube/stream.ts b/play-dl/YouTube/stream.ts index 115d059..4ccee2a 100644 --- a/play-dl/YouTube/stream.ts +++ b/play-dl/YouTube/stream.ts @@ -83,7 +83,7 @@ export async function stream_from_info( else final.push(info.format[info.format.length - 1]); let type: StreamType = final[0].codec === 'opus' && final[0].container === 'webm' ? StreamType.WebmOpus : StreamType.Arbitrary; - await request_stream(`https://${new URL(final[0].url).host}/generate_204`) + await request_stream(`https://${new URL(final[0].url).host}/generate_204`); if (options.seek) { if (type === StreamType.WebmOpus) { if (options.seek >= info.video_details.durationInSec || options.seek <= 0) diff --git a/play-dl/YouTube/utils/extractor.ts b/play-dl/YouTube/utils/extractor.ts index 3d7d835..439fcfe 100644 --- a/play-dl/YouTube/utils/extractor.ts +++ b/play-dl/YouTube/utils/extractor.ts @@ -200,6 +200,25 @@ export async function video_basic_info(url: string, options: InfoOptions = {}): } ); const microformat = player_response.microformat.playerMicroformatRenderer; + const musicInfo = + initial_response.contents.twoColumnWatchNextResults.results.results.contents?.[1]?.videoSecondaryInfoRenderer + ?.metadataRowContainer?.metadataRowContainerRenderer?.rows; + const music: any[] = []; + if (musicInfo) { + let incompleteInfo: any = {}; + musicInfo.forEach((x: any) => { + if (!x.metadataRowRenderer) return; + if (x.metadataRowRenderer.title.simpleText.toLowerCase() === 'song') { + music.push(incompleteInfo); + incompleteInfo = {}; + incompleteInfo.song = + x.metadataRowRenderer.contents[0].simpleText ?? x.metadataRowRenderer.contents[0]?.runs?.[0]?.text; + } else + incompleteInfo[x.metadataRowRenderer.title.simpleText.toLowerCase()] = + x.metadataRowRenderer.contents[0].simpleText ?? x.metadataRowRenderer.contents[0]?.runs?.[0]?.text; + }); + } + music.shift(); const video_details = new YouTubeVideo({ id: vid.videoId, title: vid.title, @@ -228,7 +247,8 @@ export async function video_basic_info(url: string, options: InfoOptions = {}): ), live: vid.isLiveContent, private: vid.isPrivate, - discretionAdvised + discretionAdvised, + music }); const format = player_response.streamingData.formats ?? []; format.push(...(player_response.streamingData.adaptiveFormats ?? [])); diff --git a/play-dl/index.ts b/play-dl/index.ts index c7e3506..615af70 100644 --- a/play-dl/index.ts +++ b/play-dl/index.ts @@ -9,7 +9,8 @@ import { YouTubeStream, YouTubeChannel, YouTubePlayList, - YouTubeVideo + YouTubeVideo, + InfoData } from './YouTube'; import { spotify, @@ -73,7 +74,6 @@ import { existsSync, mkdirSync, writeFileSync } from 'node:fs'; import { stream as yt_stream, StreamOptions, stream_from_info as yt_stream_info } from './YouTube/stream'; import { yt_search } from './YouTube/search'; import { EventEmitter } from 'stream'; -import { InfoData } from './YouTube/utils/constants'; async function stream( url: string, @@ -477,7 +477,6 @@ export { SpotifyAlbum, SpotifyPlaylist, SpotifyTrack, - YouTubeStream, YouTubeChannel, YouTubePlayList, YouTubeVideo, @@ -503,11 +502,12 @@ export { validate, video_basic_info, video_info, - yt_validate + yt_validate, + InfoData }; // Export Types -export { Deezer, YouTube, SoundCloud, Spotify }; +export { Deezer, YouTube, SoundCloud, Spotify, YouTubeStream }; // Export Default export default { From 59f3829e273dd0e2324d341e1767086672ce45a3 Mon Sep 17 00:00:00 2001 From: killer069 <65385476+killer069@users.noreply.github.com> Date: Tue, 4 Jan 2022 15:02:51 +0530 Subject: [PATCH 3/5] LiveStream issues fixed --- play-dl/YouTube/classes/LiveStream.ts | 205 ++++++++++++++------------ play-dl/YouTube/stream.ts | 4 +- play-dl/index.ts | 1 + 3 files changed, 111 insertions(+), 99 deletions(-) diff --git a/play-dl/YouTube/classes/LiveStream.ts b/play-dl/YouTube/classes/LiveStream.ts index 988991f..8f0074d 100644 --- a/play-dl/YouTube/classes/LiveStream.ts +++ b/play-dl/YouTube/classes/LiveStream.ts @@ -3,12 +3,8 @@ import { IncomingMessage } from 'node:http'; import { parseAudioFormats, StreamOptions, StreamType } from '../stream'; import { request, request_stream } from '../../Request'; import { video_stream_info } from '../utils/extractor'; +import { URL } from 'node:url'; -export interface FormatInterface { - url: string; - targetDurationSec: number; - maxDvrDurationSec: number; -} /** * YouTube Live Stream class for playing audio from Live Stream videos. */ @@ -22,29 +18,16 @@ export class LiveStream { */ type: StreamType; /** - * Base URL in dash manifest file. + * Incoming message that we recieve. + * + * Storing this is essential. + * This helps to destroy the TCP connection completely if you stopped player in between the stream */ - private base_url: string; - /** - * Given Dash URL. - */ - private url: string; - /** - * Interval to fetch data again to dash url. - */ - private interval: number; - /** - * Sequence count of urls in dash file. - */ - private packet_count: number; + private request?: IncomingMessage; /** * Timer that creates loop from interval time provided. */ - private timer: Timer; - /** - * Live Stream Video url. - */ - private video_url: string; + private normal_timer?: Timer; /** * Timer used to update dash url so as to avoid 404 errors after long hours of streaming. * @@ -52,45 +35,69 @@ export class LiveStream { */ private dash_timer: Timer; /** - * Segments of url that we recieve in dash file. - * - * base_url + segment_urls[0] = One complete url for one segment. + * Given Dash URL. */ - private segments_urls: string[]; + private dash_url: string; /** - * Incoming message that we recieve. - * - * Storing this is essential. - * This helps to destroy the TCP connection completely if you stopped player in between the stream + * Base URL in dash manifest file. */ - private request: IncomingMessage | null; + private base_url: string; + /** + * Interval to fetch data again to dash url. + */ + private interval: number; + /** + * Timer used to update dash url so as to avoid 404 errors after long hours of streaming. + * + * It updates dash_url every 30 minutes. + */ + private video_url: string; + /** + * No of segments of data to add in stream before starting to loop + */ + private precache: number; + /** + * Segment sequence number + */ + private sequence: number; /** * Live Stream Class Constructor * @param dash_url dash manifest URL * @param target_interval interval time for fetching dash data again * @param video_url Live Stream video url. */ - constructor(dash_url: string, target_interval: number, video_url: string) { + constructor(dash_url: string, interval: number, video_url: string, precache?: number) { this.stream = new Readable({ highWaterMark: 5 * 1000 * 1000, read() {} }); this.type = StreamType.Arbitrary; - this.url = dash_url; + this.sequence = 0; + this.dash_url = dash_url; this.base_url = ''; - this.segments_urls = []; - this.packet_count = 0; - this.request = null; + this.interval = interval; this.video_url = video_url; - this.interval = target_interval || 0; - this.timer = new Timer(() => { - this.start(); - }, this.interval); + this.precache = precache || 3; this.dash_timer = new Timer(() => { - this.dash_timer.reuse(); this.dash_updater(); + this.dash_timer.reuse(); }, 1800); this.stream.on('close', () => { this.cleanup(); }); - this.start(); + this.initialize_dash(); + } + /** + * This cleans every used variable in class. + * + * This is used to prevent re-use of this class and helping garbage collector to collect it. + */ + private cleanup() { + this.normal_timer?.destroy(); + this.dash_timer.destroy(); + this.request?.destroy(); + this.video_url = ''; + this.request = undefined; + this.dash_url = ''; + this.base_url = ''; + this.interval = 0; } /** * Updates dash url. @@ -99,68 +106,43 @@ export class LiveStream { */ private async dash_updater() { const info = await video_stream_info(this.video_url); - if ( - info.LiveStreamData.isLive === true && - info.LiveStreamData.hlsManifestUrl !== null && - info.video_details.durationInSec === 0 - ) { - this.url = info.LiveStreamData.dashManifestUrl as string; - } + if (info.LiveStreamData.dashManifestUrl) this.dash_url = info.LiveStreamData.dashManifestUrl; + return this.initialize_dash(); } /** - * Parses data recieved from dash_url. + * Initializes dash after getting dash url. * - * Updates base_url , segments_urls array. + * Start if it is first time of initialishing dash function. */ - private async dash_getter() { - const response = await request(this.url); + private async initialize_dash() { + const response = await request(this.dash_url); const audioFormat = response .split('')[0] .split(''); if (audioFormat[audioFormat.length - 1] === '') audioFormat.pop(); this.base_url = audioFormat[audioFormat.length - 1].split('')[1].split('')[0]; - const list = audioFormat[audioFormat.length - 1].split('')[1].split('')[0]; - this.segments_urls = list.replace(new RegExp(''); - if (this.segments_urls[this.segments_urls.length - 1] === '') this.segments_urls.pop(); - } - /** - * This cleans every used variable in class. - * - * This is used to prevent re-use of this class and helping garbage collector to collect it. - */ - private cleanup() { - this.timer.destroy(); - this.dash_timer.destroy(); - this.request?.destroy(); - this.video_url = ''; - this.request = null; - this.url = ''; - this.base_url = ''; - this.segments_urls = []; - this.packet_count = 0; - this.interval = 0; - } - /** - * This starts function in Live Stream Class. - * - * Gets data from dash url and pass it to dash getter function. - * Get data from complete segment url and pass data to Stream. - */ - private async start() { - if (this.stream.destroyed) { - this.cleanup(); - return; + await request_stream(`https://${new URL(this.base_url).host}/generate_204`); + if (this.sequence === 0) { + const list = audioFormat[audioFormat.length - 1] + .split('')[1] + .split('')[0] + .replaceAll(''); + if (list[list.length - 1] === '') list.pop(); + if (list.length > this.precache) list.splice(0, list.length - this.precache); + this.sequence = Number(list[0].split('sq/')[1].split('/')[0]); + this.first_data(list.length); } - await this.dash_getter(); - if (this.segments_urls.length > 3) this.segments_urls.splice(0, this.segments_urls.length - 3); - if (this.packet_count === 0) this.packet_count = Number(this.segments_urls[0].split('sq/')[1].split('/')[0]); - for await (const segment of this.segments_urls) { - if (Number(segment.split('sq/')[1].split('/')[0]) !== this.packet_count) { - continue; - } - await new Promise(async (resolve, reject) => { - const stream = await request_stream(this.base_url + segment).catch((err: Error) => err); + } + /** + * Used only after initializing dash function first time. + * @param len Length of data that you want to + */ + private async first_data(len: number) { + for (let i = 1; i <= len; i++) { + await new Promise(async (resolve) => { + const stream = await request_stream(this.base_url + 'sq/' + this.sequence).catch((err: Error) => err); if (stream instanceof Error) { this.stream.emit('error', stream); return; @@ -170,7 +152,7 @@ export class LiveStream { this.stream.push(c); }); stream.on('end', () => { - this.packet_count++; + this.sequence++; resolve(''); }); stream.once('error', (err) => { @@ -178,8 +160,35 @@ export class LiveStream { }); }); } - - this.timer.reuse(); + this.normal_timer = new Timer(() => { + this.loop(); + this.normal_timer?.reuse(); + }, this.interval); + } + /** + * This loops function in Live Stream Class. + * + * Gets next segment and push it. + */ + private loop() { + return new Promise(async (resolve) => { + const stream = await request_stream(this.base_url + 'sq/' + this.sequence).catch((err: Error) => err); + if (stream instanceof Error) { + this.stream.emit('error', stream); + return; + } + this.request = stream; + stream.on('data', (c) => { + this.stream.push(c); + }); + stream.on('end', () => { + this.sequence++; + resolve(''); + }); + stream.once('error', (err) => { + this.stream.emit('error', err); + }); + }); } /** * Deprecated Functions diff --git a/play-dl/YouTube/stream.ts b/play-dl/YouTube/stream.ts index 4ccee2a..2d5e0fb 100644 --- a/play-dl/YouTube/stream.ts +++ b/play-dl/YouTube/stream.ts @@ -19,6 +19,7 @@ export interface StreamOptions { quality?: number; language?: string; htmldata?: boolean; + precache?: number; } /** @@ -71,7 +72,8 @@ export async function stream_from_info( return new LiveStream( info.LiveStreamData.dashManifestUrl, info.format[info.format.length - 1].targetDurationSec as number, - info.video_details.url + info.video_details.url, + options.precache ); } diff --git a/play-dl/index.ts b/play-dl/index.ts index 615af70..5cfe36f 100644 --- a/play-dl/index.ts +++ b/play-dl/index.ts @@ -252,6 +252,7 @@ async function stream_from_info(info: InfoData, options?: StreamOptions): Promis * - `string` language : Sets language of searched content [ YouTube search only. ], e.g. "en-US" * - `number` quality : Quality number. [ 0 = Lowest, 1 = Medium, 2 = Highest ] * - `boolean` htmldata : given data is html data or not + * - `number` precache : No of segments of data to store before looping [YouTube Live Stream only]. [ Defaults to 3 ] * @returns A {@link YouTubeStream} or {@link SoundCloudStream} Stream to play */ async function stream_from_info( From ddcb71c018ea969153f188831cc4d735c78e0a77 Mon Sep 17 00:00:00 2001 From: killer069 <65385476+killer069@users.noreply.github.com> Date: Tue, 4 Jan 2022 15:13:27 +0530 Subject: [PATCH 4/5] Seek issues fixed --- play-dl/YouTube/classes/SeekStream.ts | 12 +++++++----- play-dl/YouTube/classes/WebmSeeker.ts | 5 +++++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/play-dl/YouTube/classes/SeekStream.ts b/play-dl/YouTube/classes/SeekStream.ts index 9b60e8c..62b60ef 100644 --- a/play-dl/YouTube/classes/SeekStream.ts +++ b/play-dl/YouTube/classes/SeekStream.ts @@ -97,7 +97,7 @@ export class SeekStream { if (!this.stream.headerparsed) { const stream = await request_stream(this.url, { headers: { - range: `bytes=0-1000` + range: `bytes=0-` } }).catch((err: Error) => err); @@ -112,10 +112,12 @@ export class SeekStream { this.request = stream; stream.pipe(this.stream, { end: false }); - stream.once('end', () => { - this.stream.state = WebmSeekerState.READING_DATA; - res(''); - }); + this.stream.once("headComplete", () => { + stream.unpipe(this.stream) + stream.destroy() + this.stream.state = WebmSeekerState.READING_DATA + res('') + }) } else res(''); }).catch((err) => err); if (parse instanceof Error) { diff --git a/play-dl/YouTube/classes/WebmSeeker.ts b/play-dl/YouTube/classes/WebmSeeker.ts index efef00b..9706d96 100644 --- a/play-dl/YouTube/classes/WebmSeeker.ts +++ b/play-dl/YouTube/classes/WebmSeeker.ts @@ -131,6 +131,11 @@ export class WebmSeeker extends Duplex { if (ebmlID.name === 'ebml') this.headfound = true; else return new Error('Failed to find EBML ID at start of stream.'); } + if(ebmlID.name === "cluster") { + this.emit("headComplete") + this.cursor = this.chunk.length + break; + } const data = this.chunk.slice( this.cursor + this.data_size, this.cursor + this.data_size + this.data_length From d68fb6075693ce9aff7661e2f31fcf0efc4a6811 Mon Sep 17 00:00:00 2001 From: killer069 <65385476+killer069@users.noreply.github.com> Date: Tue, 4 Jan 2022 15:20:38 +0530 Subject: [PATCH 5/5] Made setToken async --- play-dl/Spotify/index.ts | 4 ++-- play-dl/token.ts | 13 +++++++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/play-dl/Spotify/index.ts b/play-dl/Spotify/index.ts index 271f672..16b29fd 100644 --- a/play-dl/Spotify/index.ts +++ b/play-dl/Spotify/index.ts @@ -237,10 +237,10 @@ export async function refreshToken(): Promise { return true; } -export function setSpotifyToken(options: SpotifyDataOptions) { +export async function setSpotifyToken(options: SpotifyDataOptions) { spotifyData = options; spotifyData.file = false; - refreshToken(); + await refreshToken(); } export { SpotifyTrack, SpotifyAlbum, SpotifyPlaylist }; diff --git a/play-dl/token.ts b/play-dl/token.ts index 514bca7..87c4e65 100644 --- a/play-dl/token.ts +++ b/play-dl/token.ts @@ -39,14 +39,23 @@ interface tokenOptions { * } * }) // YouTube Cookies * + * await play.setToken({ + * spotify : { + * client_id: 'ID', + client_secret: 'secret', + refresh_token: 'token', + market: 'US' + * } + * }) // Await this only when setting data for spotify + * * play.setToken({ * useragent: ['Your User-agent'] * }) // Use this to avoid 429 errors. * ``` * @param options {@link tokenOptions} */ -export function setToken(options: tokenOptions) { - if (options.spotify) setSpotifyToken(options.spotify); +export async function setToken(options: tokenOptions) { + if (options.spotify) await setSpotifyToken(options.spotify); if (options.soundcloud) setSoundCloudToken(options.soundcloud); if (options.youtube) setCookieToken(options.youtube); if (options.useragent) setUserAgent(options.useragent);