diff --git a/package-lock.json b/package-lock.json index 976dde9..78e1b4a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,15 @@ { "name": "play-dl", - "version": "0.1.0", + "version": "0.1.7", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "play-dl", - "version": "0.1.0", + "version": "0.1.7", "license": "MIT", "dependencies": { "got": "^11.8.2" - }, - "devDependencies": { - "@types/node-fetch": "^2.5.12" } }, "node_modules/@sindresorhus/is": { @@ -66,16 +63,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-16.4.13.tgz", "integrity": "sha512-bLL69sKtd25w7p1nvg9pigE4gtKVpGTPojBFLMkGHXuUgap2sLqQt2qUnqmVCDfzGUL0DRNZP+1prIZJbMeAXg==" }, - "node_modules/@types/node-fetch": { - "version": "2.5.12", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.12.tgz", - "integrity": "sha512-MKgC4dlq4kKNa/mYrwpKfzQMB5X3ee5U6fSprkKpToBqBmX4nFZL9cW5jl6sWn+xpRJ7ypWh2yyqqr8UUCstSw==", - "dev": true, - "dependencies": { - "@types/node": "*", - "form-data": "^3.0.0" - } - }, "node_modules/@types/responselike": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", @@ -84,12 +71,6 @@ "@types/node": "*" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, "node_modules/cacheable-lookup": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", @@ -123,18 +104,6 @@ "mimic-response": "^1.0.0" } }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/decompress-response": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", @@ -168,15 +137,6 @@ "node": ">=10" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -185,20 +145,6 @@ "once": "^1.4.0" } }, - "node_modules/form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/get-stream": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", @@ -275,27 +221,6 @@ "node": ">=8" } }, - "node_modules/mime-db": { - "version": "1.49.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz", - "integrity": "sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.32", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz", - "integrity": "sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==", - "dev": true, - "dependencies": { - "mime-db": "1.49.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/mimic-response": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", @@ -413,16 +338,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-16.4.13.tgz", "integrity": "sha512-bLL69sKtd25w7p1nvg9pigE4gtKVpGTPojBFLMkGHXuUgap2sLqQt2qUnqmVCDfzGUL0DRNZP+1prIZJbMeAXg==" }, - "@types/node-fetch": { - "version": "2.5.12", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.12.tgz", - "integrity": "sha512-MKgC4dlq4kKNa/mYrwpKfzQMB5X3ee5U6fSprkKpToBqBmX4nFZL9cW5jl6sWn+xpRJ7ypWh2yyqqr8UUCstSw==", - "dev": true, - "requires": { - "@types/node": "*", - "form-data": "^3.0.0" - } - }, "@types/responselike": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", @@ -431,12 +346,6 @@ "@types/node": "*" } }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, "cacheable-lookup": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", @@ -464,15 +373,6 @@ "mimic-response": "^1.0.0" } }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, "decompress-response": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", @@ -493,12 +393,6 @@ "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==" }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true - }, "end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -507,17 +401,6 @@ "once": "^1.4.0" } }, - "form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, "get-stream": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", @@ -576,21 +459,6 @@ "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" }, - "mime-db": { - "version": "1.49.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz", - "integrity": "sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==", - "dev": true - }, - "mime-types": { - "version": "2.1.32", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz", - "integrity": "sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==", - "dev": true, - "requires": { - "mime-db": "1.49.0" - } - }, "mimic-response": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", diff --git a/package.json b/package.json index 1999ec3..c213cd7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "play-dl", - "version": "0.1.0", + "version": "0.1.7", "description": "YouTube, SoundCloud, Spotify downloader", "main": "dist/index.js", "typings": "dist/index.d.ts", @@ -33,8 +33,5 @@ ], "dependencies": { "got": "^11.8.2" - }, - "devDependencies": { - "@types/node-fetch": "^2.5.12" } } diff --git a/play-dl/YouTube/classes/LiveStream.ts b/play-dl/YouTube/classes/LiveStream.ts new file mode 100644 index 0000000..706641d --- /dev/null +++ b/play-dl/YouTube/classes/LiveStream.ts @@ -0,0 +1,139 @@ +import { PassThrough } from 'stream' +import got from 'got' + +export interface FormatInterface{ + url : string; + targetDurationSec : number; + maxDvrDurationSec : number +} + +export class LiveStreaming{ + smooth : boolean; + private __stream : PassThrough + private format : FormatInterface + private interval : number + private packet_count : number + private timer : NodeJS.Timer | null + private segments_urls : string[] + constructor(format : FormatInterface, smooth : boolean){ + this.smooth = smooth || false + this.format = format + this.__stream = new PassThrough({ highWaterMark : 10 * 1000 * 1000 }) + this.segments_urls = [] + this.packet_count = 0 + this.interval = 0 + this.timer = null + this.__stream.on('close', () => { + this.cleanup() + }) + if(this.smooth === true) this.__stream.pause() + this.start() + } + + async manifest_getter(){ + let response = await got(this.format.url) + this.segments_urls = response.body.split('\n').filter((x) => x.startsWith('https')) + } + + get stream(){ + return this.__stream + } + + private cleanup(){ + clearInterval(this.timer as NodeJS.Timer) + this.segments_urls = [] + this.packet_count = 0 + } + + async start(){ + if(this.__stream.destroyed) this.cleanup() + await this.manifest_getter() + if(this.packet_count === 0) this.packet_count = Number(this.segments_urls[0].split('index.m3u8/sq/')[1].split('/')[0]) + for await (let url of this.segments_urls){ + await (async () => { + return new Promise(async (resolve, reject) => { + if(Number(url.split('index.m3u8/sq/')[1].split('/')[0]) !== this.packet_count){ + resolve('') + return + } + let stream = this.got_stream(url) + stream.on('data', (chunk) => this.__stream.write(chunk)) + stream.on('end', () => { + this.packet_count++ + resolve('') + }) + }) + })() + } + this.interval = (this.segments_urls.length / 2) * 1000 + this.timer = setTimeout(async () => { + if(this.smooth === true){ + this.__stream.resume() + this.smooth = false + } + await this.start() + }, this.interval) + } + + private got_stream(url: string){ + return got.stream(url) + } +} + +export class LiveEnded{ + private __stream : PassThrough + private format : FormatInterface + private packet_count : number + private segments_urls : string[] + constructor(format : FormatInterface){ + this.format = format + this.__stream = new PassThrough({ highWaterMark : 10 * 1000 * 1000 }) + this.segments_urls = [] + this.packet_count = 0 + this.__stream.on('close', () => { + this.cleanup() + }) + this.start() + } + + async manifest_getter(){ + let response = await got(this.format.url) + this.segments_urls = response.body.split('\n').filter((x) => x.startsWith('https')) + } + + get stream(){ + return this.__stream + } + + private cleanup(){ + this.segments_urls = [] + this.packet_count = 0 + } + + async start(){ + if(this.__stream.destroyed) this.cleanup() + await this.manifest_getter() + if(this.packet_count === 0) this.packet_count = Number(this.segments_urls[0].split('index.m3u8/sq/')[1].split('/')[0]) + for await (let url of this.segments_urls){ + await (async () => { + return new Promise(async (resolve, reject) => { + if(Number(url.split('index.m3u8/sq/')[1].split('/')[0]) !== this.packet_count){ + resolve('') + return + } + let stream = this.got_stream(url) + stream.on('data', (chunk) => this.__stream.write(chunk)) + stream.on('end', () => { + this.packet_count++ + resolve('') + }) + }) + })() + } + } + + private got_stream(url: string){ + return got.stream(url) + } +} + diff --git a/play-dl/YouTube/index.ts b/play-dl/YouTube/index.ts index 9459d6a..8cfd2ce 100644 --- a/play-dl/YouTube/index.ts +++ b/play-dl/YouTube/index.ts @@ -1,5 +1,3 @@ -import { stream } from './stream' - export { search } from './search' -export { stream } +export { stream, stream_from_info, stream_type } from './stream' export * from './utils' \ No newline at end of file diff --git a/play-dl/YouTube/stream.ts b/play-dl/YouTube/stream.ts index 7150527..4964d8b 100644 --- a/play-dl/YouTube/stream.ts +++ b/play-dl/YouTube/stream.ts @@ -1,87 +1,149 @@ import got from "got" import { video_info } from "." +import { PassThrough } from 'stream' +import https from 'https' +import { FormatInterface, LiveEnded, LiveStreaming } from "./classes/LiveStream" - -interface FilterOptions { - averagebitrate? : number; - videoQuality? : "144p" | "240p" | "360p" | "480p" | "720p" | "1080p"; - audioQuality? : "AUDIO_QUALITY_LOW" | "AUDIO_QUALITY_MEDIUM"; - audioSampleRate? : number; - audioChannels? : number; - audioCodec? : string; - audioContainer? : string; - hasAudio? : boolean; - hasVideo? : boolean; - isLive? : boolean; +enum StreamType{ + Arbitrary = 'arbitrary', + Raw = 'raw', + OggOpus = 'ogg/opus', + WebmOpus = 'webm/opus', + Opus = 'opus', } interface StreamOptions { - filter : "bestaudio" | "bestvideo" + smooth : boolean } -function parseFormats(formats : any[]): { audio: any[], video:any[] } { - let audio: any[] = [] - let video: any[] = [] +interface InfoData{ + LiveStreamData : { + isLive : boolean + dashManifestUrl : string + hlsManifestUrl : string + } + html5player : string + format : any[] + video_details : any +} + +function parseAudioFormats(formats : any[]){ + let result: any[] = [] formats.forEach((format) => { let type = format.mimeType as string if(type.startsWith('audio')){ - format.audioCodec = type.split('codecs="')[1].split('"')[0] - format.audioContainer = type.split('audio/')[1].split(';')[0] - format.hasAudio = true - format.hasVideo = false - audio.push(format) - } - else if(type.startsWith('video')){ - format.videoQuality = format.qualityLabel - format.hasAudio = false - format.hasVideo = true - video.push(format) + format.codec = type.split('codecs="')[1].split('"')[0] + format.container = type.split('audio/')[1].split(';')[0] + result.push(format) } }) - return { audio, video } + return result } -function filter_songs(formats : any[], options : FilterOptions) { -} - -export async function stream(url : string, options? : StreamOptions){ +export async function stream(url : string, options : StreamOptions = { smooth : false }): Promise{ let info = await video_info(url) let final: any[] = []; - - if(options?.filter === 'bestaudio'){ - info.format.forEach((format) => { - let type = format.mimeType as string - if(type.startsWith('audio/webm')){ - return final.push(format) - } - else return - }) - - if(final.length === 0){ - info.format.forEach((format) => { - let type = format.mimeType as string - if(type.startsWith('audio/')){ - return final.push(format) - } - else return - }) - } + + if(info.LiveStreamData.isLive === true && info.LiveStreamData.hlsManifestUrl !== null) { + return await live_stream(info as InfoData, options.smooth) } - else if(options?.filter === 'bestvideo'){ - info.format.forEach((format) => { - let type = format.mimeType as string - if(type.startsWith('video/')){ - if(parseInt(format.qualityLabel) > 480) final.push(format) - else return - } - else return - }) - if(final.length === 0) throw new Error("Video Format > 480p is not found") + let audioFormat = parseAudioFormats(info.format) + let opusFormats = filterFormat(audioFormat, "opus") + + if(opusFormats.length === 0){ + final.push(audioFormat[audioFormat.length - 1]) } else{ - final.push(info.format[info.format.length - 1]) + final.push(opusFormats[opusFormats.length - 1]) } - return got.stream(final[0].url) + if(final.length === 0) final.push(info.format[info.format.length - 1]) + let piping_stream = got.stream(final[0].url, { + retry : 5, + headers: { + 'Connection': 'keep-alive', + 'Accept-Encoding': '', + 'Accept-Language': 'en-US,en;q=0.8', + 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36' + }, + agent : { + https : new https.Agent({ keepAlive : true }) + }, + http2 : true + }) + let playing_stream = new PassThrough({ highWaterMark: 10 * 1000 * 1000 }) + + piping_stream.pipe(playing_stream) + return playing_stream +} + +export async function stream_from_info(info : InfoData, options : StreamOptions){ + let final: any[] = []; + + if(info.LiveStreamData.isLive === true) { + return await live_stream(info as InfoData, options.smooth) + } + + let audioFormat = parseAudioFormats(info.format) + let opusFormats = filterFormat(audioFormat, "opus") + + if(opusFormats.length === 0){ + final.push(audioFormat[audioFormat.length - 1]) + } + else{ + final.push(opusFormats[opusFormats.length - 1]) + } + + if(final.length === 0) final.push(info.format[info.format.length - 1]) + let piping_stream = got.stream(final[0].url, { + retry : 5, + headers: { + 'Connection': 'keep-alive', + 'Accept-Encoding': '', + 'Accept-Language': 'en-US,en;q=0.8', + 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36' + }, + agent : { + https : new https.Agent({ keepAlive : true }) + }, + http2 : true + }) + let playing_stream = new PassThrough({ highWaterMark: 10 * 1000 * 1000 }) + + piping_stream.pipe(playing_stream) + return playing_stream +} + +function filterFormat(formats : any[], codec : string){ + let result: any[] = [] + formats.forEach((format) => { + if(format.codec === codec) result.push(format) + }) + return result +} + +export function stream_type(info:InfoData): StreamType{ + if(info.LiveStreamData.isLive === true && info.LiveStreamData.hlsManifestUrl !== null) return StreamType.Arbitrary + else return StreamType.WebmOpus +} + +async function live_stream(info : InfoData, smooth : boolean): Promise{ + let res_144 : FormatInterface = { + url : '', + targetDurationSec : 0, + maxDvrDurationSec : 0 + } + info.format.forEach((format) => { + if(format.qualityLabel === '144p') res_144 = format + else return + }) + let stream : LiveStreaming | LiveEnded + if(info.video_details.duration === '0') { + stream = new LiveStreaming((res_144.url.length !== 0) ? res_144 : info.format[info.format.length - 1], smooth) + } + else { + stream = new LiveEnded((res_144.url.length !== 0) ? res_144 : info.format[info.format.length - 1]) + } + return stream.stream } \ No newline at end of file diff --git a/play-dl/YouTube/utils/cipher.ts b/play-dl/YouTube/utils/cipher.ts index 0c2cb98..214b692 100644 --- a/play-dl/YouTube/utils/cipher.ts +++ b/play-dl/YouTube/utils/cipher.ts @@ -1,6 +1,6 @@ -import { URL } from 'node:url' +import { URL } from 'url' import { url_get } from './request' -import querystring from 'node:querystring' +import querystring from 'querystring' interface formatOptions { url? : string; diff --git a/play-dl/YouTube/utils/extractor.ts b/play-dl/YouTube/utils/extractor.ts index 58bad70..e616a28 100644 --- a/play-dl/YouTube/utils/extractor.ts +++ b/play-dl/YouTube/utils/extractor.ts @@ -12,12 +12,11 @@ export async function video_basic_info(url : string){ let video_id = url.split('watch?v=')[1].split('&')[0] let new_url = 'https://www.youtube.com/watch?v=' + video_id let body = await url_get(new_url) - let player_response = JSON.parse(body.split("var ytInitialPlayerResponse = ")[1].split(";")[0]) + let player_response = JSON.parse(body.split("var ytInitialPlayerResponse = ")[1].split("}};")[0] + '}}') if(player_response.playabilityStatus.status === 'ERROR') throw new Error(`While getting info from url \n ${player_response.playabilityStatus.reason}`) + if(player_response.playabilityStatus.status === 'LOGIN_REQUIRED') throw new Error(`While getting info from url \n ${player_response.playabilityStatus.messages[0]}`) let html5player = 'https://www.youtube.com' + body.split('"jsUrl":"')[1].split('"')[0] let format = [] - format.push(player_response.streamingData.formats[0]) - format.push(...player_response.streamingData.adaptiveFormats) let vid = player_response.videoDetails let microformat = player_response.microformat.playerMicroformatRenderer let video_details = { @@ -30,7 +29,7 @@ export async function video_basic_info(url : string){ thumbnail : { width : vid.thumbnail.thumbnails[vid.thumbnail.thumbnails.length - 1].width, height : vid.thumbnail.thumbnails[vid.thumbnail.thumbnails.length - 1].height, - url : `https://i.ytimg.com/vi/${vid.videoId}/maxresdefault.jpg` + url : vid.thumbnail.thumbnails[vid.thumbnail.thumbnails.length - 1].url, }, channel : { name : vid.author, @@ -43,7 +42,15 @@ export async function video_basic_info(url : string){ live : vid.isLiveContent, private : vid.isPrivate } + if(!video_details.live) format.push(player_response.streamingData.formats[0]) + format.push(...player_response.streamingData.adaptiveFormats) + let LiveStreamData = { + isLive : video_details.live, + dashManifestUrl : (player_response.streamingData?.dashManifestUrl) ? player_response.streamingData?.dashManifestUrl : null, + hlsManifestUrl : (player_response.streamingData?.hlsManifestUrl) ? player_response.streamingData?.hlsManifestUrl : null + } return { + LiveStreamData, html5player, format, video_details @@ -56,11 +63,35 @@ export async function video_info(url : string) { data.format = await format_decipher(data.format, data.html5player) return data } + else if(data.LiveStreamData.isLive === true && data.LiveStreamData.hlsManifestUrl !== null){ + let m3u8 = await url_get(data.LiveStreamData.hlsManifestUrl) + data.format = await parseM3U8(m3u8, data.format) + return data + } else { return data } } +async function parseM3U8(m3u8_data : string, formats : any[]): Promise{ + let lines = m3u8_data.split('\n') + formats.forEach((format) => { + if(!format.qualityLabel) return + let reso = format.width + 'x' + format.height + let index = -1; + let line_count = 0 + lines.forEach((line) => { + index = line.search(reso) + if(index !== -1) { + format.url = lines[line_count+1] + } + line_count++ + index = -1 + }) + }) + return formats +} + export async function playlist_info(url : string) { if (!url || typeof url !== "string") throw new Error(`Expected playlist url, received ${typeof url}!`); if(url.search('(\\?|\\&)list\\=') === -1) throw new Error('This is not a PlayList URL') diff --git a/play-dl/YouTube/utils/request.ts b/play-dl/YouTube/utils/request.ts index 2041b0b..999c8f7 100644 --- a/play-dl/YouTube/utils/request.ts +++ b/play-dl/YouTube/utils/request.ts @@ -3,7 +3,6 @@ import got, { OptionsOfTextResponseBody } from 'got/dist/source' export async function url_get (url : string, options? : OptionsOfTextResponseBody) : Promise{ return new Promise(async(resolve, reject) => { let response = await got(url, options) - if(response.statusCode === 200) { resolve(response.body) } diff --git a/play-dl/index.ts b/play-dl/index.ts index 47a0e0d..7e28b2f 100644 --- a/play-dl/index.ts +++ b/play-dl/index.ts @@ -1,4 +1,4 @@ //This File is in testing stage, everything will change in this -import { playlist_info, video_basic_info, video_info, search, stream } from "./YouTube"; +import { playlist_info, video_basic_info, video_info, search, stream, stream_from_info, stream_type } from "./YouTube"; -export var youtube = { playlist_info, video_basic_info, video_info, search , stream} +export let youtube = { playlist_info, video_basic_info, video_info, search , stream, stream_from_info, stream_type}