Long Playback fixes
version 0.5.0 update
This commit is contained in:
commit
4d61252c31
@ -195,16 +195,96 @@ export class LiveEnded{
|
|||||||
|
|
||||||
export class Stream {
|
export class Stream {
|
||||||
type : StreamType
|
type : StreamType
|
||||||
private piping_stream : Request
|
stream : PassThrough
|
||||||
private playing_stream : PassThrough
|
private url : string
|
||||||
constructor(url : string, type : StreamType){
|
private bytes_count : number;
|
||||||
|
private per_sec_bytes : number;
|
||||||
|
private duration : number;
|
||||||
|
private timer : NodeJS.Timer | null
|
||||||
|
constructor(url : string, type : StreamType, duration : number){
|
||||||
|
this.url = url
|
||||||
this.type = type
|
this.type = type
|
||||||
this.piping_stream = got.stream(url)
|
this.stream = new PassThrough({ highWaterMark : 10 * 1000 * 1000 })
|
||||||
this.playing_stream = new PassThrough({ highWaterMark : 10 * 1000 * 1000 })
|
this.bytes_count = 0
|
||||||
this.piping_stream.pipe(this.playing_stream)
|
this.per_sec_bytes = 0
|
||||||
|
this.timer = null
|
||||||
|
this.duration = duration;
|
||||||
|
(duration > 300) ? this.loop_start() : this.normal_start()
|
||||||
}
|
}
|
||||||
|
|
||||||
get stream(){
|
private cleanup(){
|
||||||
return this.playing_stream
|
clearTimeout(this.timer as NodeJS.Timer)
|
||||||
|
this.timer = null
|
||||||
|
this.url = ''
|
||||||
|
this.bytes_count = 0
|
||||||
|
this.per_sec_bytes = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
private normal_start(){
|
||||||
|
if(this.stream.destroyed){
|
||||||
|
this.cleanup()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let stream = got.stream(this.url)
|
||||||
|
stream.pipe(this.stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
private loop_start(){
|
||||||
|
if(this.stream.destroyed){
|
||||||
|
this.cleanup()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let stream = got.stream(this.url)
|
||||||
|
stream.once('data', () => {
|
||||||
|
this.per_sec_bytes = Math.ceil((stream.downloadProgress.total as number)/this.duration)
|
||||||
|
})
|
||||||
|
|
||||||
|
stream.on('data', (chunk) => {
|
||||||
|
this.bytes_count += chunk.length
|
||||||
|
this.stream.write(chunk)
|
||||||
|
})
|
||||||
|
|
||||||
|
stream.on('data', () => {
|
||||||
|
if(this.bytes_count > (this.per_sec_bytes * 300)){
|
||||||
|
stream.destroy()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.timer = setTimeout(() => {
|
||||||
|
this.loop()
|
||||||
|
}, 270 * 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
private loop(){
|
||||||
|
if(this.stream.destroyed){
|
||||||
|
this.cleanup()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let absolute_bytes : number = 0
|
||||||
|
let stream = got.stream(this.url, {
|
||||||
|
headers : {
|
||||||
|
"range" : `bytes=${this.bytes_count}-`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
stream.on('data', (chunk) => {
|
||||||
|
absolute_bytes += chunk.length
|
||||||
|
this.bytes_count += chunk.length
|
||||||
|
this.stream.write(chunk)
|
||||||
|
})
|
||||||
|
|
||||||
|
stream.on('data', () => {
|
||||||
|
if(absolute_bytes > (this.per_sec_bytes * 300)){
|
||||||
|
stream.destroy()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
stream.on('end', () => {
|
||||||
|
this.cleanup()
|
||||||
|
})
|
||||||
|
|
||||||
|
this.timer = setTimeout(() => {
|
||||||
|
this.loop()
|
||||||
|
}, 270 * 1000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,8 +6,8 @@ interface VideoOptions {
|
|||||||
url? : string;
|
url? : string;
|
||||||
title?: string;
|
title?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
duration_formatted: string;
|
durationRaw: string;
|
||||||
duration: number;
|
durationInSec: number;
|
||||||
uploadedAt?: string;
|
uploadedAt?: string;
|
||||||
views: number;
|
views: number;
|
||||||
thumbnail?: {
|
thumbnail?: {
|
||||||
@ -37,8 +37,8 @@ export class Video {
|
|||||||
url? : string;
|
url? : string;
|
||||||
title?: string;
|
title?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
durationFormatted: string;
|
durationRaw: string;
|
||||||
duration: number;
|
durationInSec: number;
|
||||||
uploadedAt?: string;
|
uploadedAt?: string;
|
||||||
views: number;
|
views: number;
|
||||||
thumbnail?: Thumbnail;
|
thumbnail?: Thumbnail;
|
||||||
@ -57,8 +57,8 @@ export class Video {
|
|||||||
this.url = `https://www.youtube.com/watch?v=${this.id}`
|
this.url = `https://www.youtube.com/watch?v=${this.id}`
|
||||||
this.title = data.title || undefined;
|
this.title = data.title || undefined;
|
||||||
this.description = data.description || undefined;
|
this.description = data.description || undefined;
|
||||||
this.durationFormatted = data.duration_raw || "0:00";
|
this.durationRaw = data.duration_raw || "0:00";
|
||||||
this.duration = (data.duration < 0 ? 0 : data.duration) || 0;
|
this.durationInSec = (data.duration < 0 ? 0 : data.duration) || 0;
|
||||||
this.uploadedAt = data.uploadedAt || undefined;
|
this.uploadedAt = data.uploadedAt || undefined;
|
||||||
this.views = parseInt(data.views) || 0;
|
this.views = parseInt(data.views) || 0;
|
||||||
this.thumbnail = data.thumbnail || {};
|
this.thumbnail = data.thumbnail || {};
|
||||||
@ -84,8 +84,8 @@ export class Video {
|
|||||||
url: this.url,
|
url: this.url,
|
||||||
title: this.title,
|
title: this.title,
|
||||||
description: this.description,
|
description: this.description,
|
||||||
duration: this.duration,
|
durationInSec: this.durationInSec,
|
||||||
duration_formatted: this.durationFormatted,
|
durationRaw: this.durationRaw,
|
||||||
uploadedAt: this.uploadedAt,
|
uploadedAt: this.uploadedAt,
|
||||||
thumbnail: this.thumbnail?.toJSON(),
|
thumbnail: this.thumbnail?.toJSON(),
|
||||||
channel: {
|
channel: {
|
||||||
|
|||||||
@ -65,7 +65,7 @@ export async function stream(url : string, options : StreamOptions = { low_laten
|
|||||||
final.push(info.format[info.format.length - 1])
|
final.push(info.format[info.format.length - 1])
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Stream(final[0].url, type)
|
return new Stream(final[0].url, type, info.video_details.durationInSec)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function stream_from_info(info : InfoData, options : StreamOptions = { low_latency : false, preferred_quality : "144p" }): Promise<Stream | LiveStreaming | LiveEnded>{
|
export async function stream_from_info(info : InfoData, options : StreamOptions = { low_latency : false, preferred_quality : "144p" }): Promise<Stream | LiveStreaming | LiveEnded>{
|
||||||
@ -94,7 +94,7 @@ export async function stream_from_info(info : InfoData, options : StreamOptions
|
|||||||
final.push(info.format[info.format.length - 1])
|
final.push(info.format[info.format.length - 1])
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Stream(final[0].url, type)
|
return new Stream(final[0].url, type, info.video_details.durationInSec)
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterFormat(formats : any[], codec : string){
|
function filterFormat(formats : any[], codec : string){
|
||||||
|
|||||||
@ -8,7 +8,10 @@ const video_pattern = /^((?:https?:)?\/\/)?(?:(?:www|m)\.)?((?:youtube\.com|yout
|
|||||||
|
|
||||||
export async function video_basic_info(url : string){
|
export async function video_basic_info(url : string){
|
||||||
if(!url.match(video_pattern)) throw new Error('This is not a YouTube URL')
|
if(!url.match(video_pattern)) throw new Error('This is not a YouTube URL')
|
||||||
let video_id = url.split('watch?v=')[1].split('&')[0]
|
let video_id : string;
|
||||||
|
if(url.includes('youtu.be/')) video_id = url.split('youtu.be/')[1]
|
||||||
|
else if(url.includes('youtube.com/embed/')) video_id = url.split('youtube.com/embed/')[1]
|
||||||
|
else video_id = url.split('watch?v=')[1].split('&')[0];
|
||||||
let new_url = 'https://www.youtube.com/watch?v=' + video_id
|
let new_url = 'https://www.youtube.com/watch?v=' + video_id
|
||||||
let body = await url_get(new_url)
|
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] + '}}')
|
||||||
@ -23,7 +26,8 @@ export async function video_basic_info(url : string){
|
|||||||
url : 'https://www.youtube.com/watch?v=' + vid.videoId,
|
url : 'https://www.youtube.com/watch?v=' + vid.videoId,
|
||||||
title : vid.title,
|
title : vid.title,
|
||||||
description : vid.shortDescription,
|
description : vid.shortDescription,
|
||||||
duration : vid.lengthSeconds,
|
durationInSec : vid.lengthSeconds,
|
||||||
|
durationRaw : parseSeconds(vid.lengthSeconds),
|
||||||
uploadedDate : microformat.publishDate,
|
uploadedDate : microformat.publishDate,
|
||||||
thumbnail : {
|
thumbnail : {
|
||||||
width : vid.thumbnail.thumbnails[vid.thumbnail.thumbnails.length - 1].width,
|
width : vid.thumbnail.thumbnails[vid.thumbnail.thumbnails.length - 1].width,
|
||||||
@ -56,6 +60,18 @@ export async function video_basic_info(url : string){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseSeconds(seconds : number): string {
|
||||||
|
let d = Number(seconds);
|
||||||
|
var h = Math.floor(d / 3600);
|
||||||
|
var m = Math.floor(d % 3600 / 60);
|
||||||
|
var s = Math.floor(d % 3600 % 60);
|
||||||
|
|
||||||
|
var hDisplay = h > 0 ? (h < 10 ? `0${h}` : h) + ':' : "";
|
||||||
|
var mDisplay = m > 0 ? (m < 10 ? `0${m}` : m) + ':' : "00:";
|
||||||
|
var sDisplay = s > 0 ? (s < 10 ? `0${s}` : s) : "00";
|
||||||
|
return hDisplay + mDisplay + sDisplay;
|
||||||
|
}
|
||||||
|
|
||||||
export async function video_info(url : string) {
|
export async function video_info(url : string) {
|
||||||
let data = await video_basic_info(url)
|
let data = await video_basic_info(url)
|
||||||
if(data.LiveStreamData.isLive === true && data.LiveStreamData.hlsManifestUrl !== null){
|
if(data.LiveStreamData.isLive === true && data.LiveStreamData.hlsManifestUrl !== null){
|
||||||
@ -100,7 +116,10 @@ export async function playlist_info(url : string) {
|
|||||||
|
|
||||||
let body = await url_get(new_url)
|
let body = await url_get(new_url)
|
||||||
let response = JSON.parse(body.split("var ytInitialData = ")[1].split(";</script>")[0])
|
let response = JSON.parse(body.split("var ytInitialData = ")[1].split(";</script>")[0])
|
||||||
if(response.alerts && response.alerts[0].alertRenderer.type === 'ERROR') throw new Error(`While parsing playlist url\n ${response.alerts[0].alertRenderer.text.runs[0].text}`)
|
if(response.alerts){
|
||||||
|
if(response.alerts[0].alertRenderer?.type === 'ERROR') throw new Error(`While parsing playlist url\n ${response.alerts[0].alertRenderer.text.runs[0].text}`)
|
||||||
|
else throw new Error('While parsing playlist url\n Unknown Playlist Error')
|
||||||
|
}
|
||||||
|
|
||||||
let rawJSON = `${body.split('{"playlistVideoListRenderer":{"contents":')[1].split('}],"playlistId"')[0]}}]`;
|
let rawJSON = `${body.split('{"playlistVideoListRenderer":{"contents":')[1].split('}],"playlistId"')[0]}}]`;
|
||||||
let parsed = JSON.parse(rawJSON);
|
let parsed = JSON.parse(rawJSON);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user