From 4289aa9c3a327f134ac455e4790951088100928a Mon Sep 17 00:00:00 2001 From: ThePedroo Date: Mon, 1 Apr 2024 02:31:23 -0300 Subject: [PATCH] add: `TrackStartEvent` `track.metadata` This commit adds the metadata field for track in "TrackStartEvent" event. --- src/connection/voiceHandler.js | 13 +++++++++-- src/sources.js | 21 ++++++++--------- src/sources/deezer.js | 7 +++++- src/sources/http.js | 4 +--- src/sources/soundcloud.js | 12 +++++++--- src/sources/youtube.js | 42 +++++++++++++++++++++++----------- src/utils.js | 17 +++++++++++--- 7 files changed, 79 insertions(+), 37 deletions(-) diff --git a/src/connection/voiceHandler.js b/src/connection/voiceHandler.js index 42ce79c..fec04cb 100644 --- a/src/connection/voiceHandler.js +++ b/src/connection/voiceHandler.js @@ -178,7 +178,10 @@ class VoiceConnection { if (streamInfo.exception) return streamInfo - return { stream: voiceUtils.createAudioResource(streamInfo.stream, urlInfo.format) } + return { + stream: voiceUtils.createAudioResource(streamInfo.stream, urlInfo.format), + bitrate: streamInfo.bitrate + } } async play(track, decodedTrack, noReplace) { @@ -284,7 +287,13 @@ class VoiceConnection { this.cache.url = urlInfo.url this.cache.protocol = urlInfo.protocol - this.config.track = { encoded: track, info: decodedTrack } + this.config.track = { + encoded: track, + info: decodedTrack, + metadata: { + bitrate: resource.bitrate + } + } this.config.paused = false if (this.config.volume !== 100) diff --git a/src/sources.js b/src/sources.js index 82b16f7..70c648c 100644 --- a/src/sources.js +++ b/src/sources.js @@ -119,26 +119,18 @@ function getTrackStream(decodedTrack, url, protocol, additionalData) { let trueSource = [ 'pandora', 'spotify' ].includes(decodedTrack.sourceName) ? config.search.defaultSearchSource : decodedTrack.sourceName if (trueSource === 'youtube' && protocol === 'hls') { - return resolve({ - stream: await youtube.loadStream(url) - }) + return resolve(await youtube.loadStream(url)) } if (trueSource === 'deezer') { - return resolve({ - stream: await deezer.loadTrack(decodedTrack.title, url, additionalData) - }) + return resolve(await deezer.loadTrack(decodedTrack.title, url, additionalData)) } if (trueSource === 'soundcloud') { if (additionalData === true) { trueSource = config.search.fallbackSearchSource } else if (protocol === 'hls') { - const stream = await soundcloud.loadHLSStream(url) - - return resolve({ - stream - }) + return resolve(await soundcloud.loadHLSStream(url)) } } @@ -182,8 +174,13 @@ function getTrackStream(decodedTrack, url, protocol, additionalData) { }) }) + console.log(res.headers['content-length'], decodedTrack.duration) + resolve({ - stream + stream, + bitrate: decodedTrack.sourceName !== 'http' ? Math.round((res.headers['content-length'] * 8) / decodedTrack.duration / 1000) : null, + protocol, + size: res.headers['content-length'] }) } } catch (err) { diff --git a/src/sources/deezer.js b/src/sources/deezer.js index 678ea0f..1f56a3c 100644 --- a/src/sources/deezer.js +++ b/src/sources/deezer.js @@ -365,6 +365,8 @@ function loadTrack(title, url, trackInfos) { streamOnly: true }) + const bitrate = Math.round((res.headers['content-length'] * 8) / trackInfos.DURATION) + res.stream.on('end', () => stream.end()) res.stream.on('error', (error) => { debugLog('retrieveStream', 4, { type: 2, sourceName: 'Deezer', query: title, message: error.message }) @@ -413,7 +415,10 @@ function loadTrack(title, url, trackInfos) { } } - resolve(stream) + resolve({ + stream, + bitrate + }) }) }) } diff --git a/src/sources/http.js b/src/sources/http.js index f7d11ba..8f97d54 100644 --- a/src/sources/http.js +++ b/src/sources/http.js @@ -1,7 +1,6 @@ import { debugLog, makeRequest, encodeTrack } from '../utils.js' async function loadFrom(uri) { - const type = uri.startsWith('http://') ? 'http' : 'https' debugLog('loadtracks', 4, { type: 1, loadType: 'track', sourceName: type, query: uri }) const data = await makeRequest(uri, { method: 'HEAD' }) @@ -19,7 +18,6 @@ async function loadFrom(uri) { } } - if (!data.headers || !data.headers['content-type']?.startsWith('audio/')) { debugLog('loadtracks', 4, { type: 2, loadType: 'error', sourceName: type, query: uri, message: 'Url is not a playable stream.' }) @@ -44,7 +42,7 @@ async function loadFrom(uri) { uri, artworkUrl: null, isrc: null, - sourceName: type + sourceName: 'http' } debugLog('loadtracks', 4, { type: 2, loadType: 'track', sourceName: type, track, query: uri }) diff --git a/src/sources/soundcloud.js b/src/sources/soundcloud.js index f0ce8bd..215d43d 100644 --- a/src/sources/soundcloud.js +++ b/src/sources/soundcloud.js @@ -325,7 +325,8 @@ async function retrieveStream(identifier, title) { url: urlInfo.url, protocol: urlInfo.protocol, format: urlInfo.format, - additionalData: true + additionalData: true, + bitrate: urlInfo.bitrate } } } @@ -341,9 +342,14 @@ async function loadHLSStream(url) { const streamHlsRedirect = await http1makeRequest(url, { method: 'GET' }) const stream = new PassThrough() - await loadHLS(streamHlsRedirect.body.url, stream) + const metadata = await loadHLS(streamHlsRedirect.body.url, stream) - return stream + return { + stream, + ...metadata, + protocol: 'hls', + size: -1 + } } async function loadFilters(url, protocol) { diff --git a/src/sources/youtube.js b/src/sources/youtube.js index bcb5146..d2c5e8c 100644 --- a/src/sources/youtube.js +++ b/src/sources/youtube.js @@ -12,9 +12,16 @@ const ytContext = { } : {}), client: { ...(!config.search.sources.youtube.bypassAgeRestriction ? { - userAgent: 'com.google.android.youtube/19.13.34 (Linux; U; Android 14 gzip)', + userAgent: 'com.google.android.youtube/19.13.35 (Linux; U; Android 14 gzip)', clientName: 'ANDROID', - clientVersion: '19.13.34', + clientVersion: '19.13.35', + deviceMake: 'Google', + deviceModel: 'Pixel 8', + onName: 'Android', + osVersion: '14', + gl: 'BR', + hl: 'pt', + timezone: 'America/Sao_Paulo' } : { clientName: 'TVHTML5_SIMPLY_EMBEDDED_PLAYER', clientVersion: '2.0', @@ -47,12 +54,12 @@ function _getBaseHost(type) { function _switchClient(newClient) { if (newClient === 'ANDROID') { ytContext.client.clientName = 'ANDROID' - ytContext.client.clientVersion = '19.04.33' - ytContext.client.userAgent = 'com.google.android.youtube/19.04.33 (Linux; U; Android 14 gzip)' + ytContext.client.clientVersion = '19.13.35' + ytContext.client.userAgent = 'com.google.android.youtube/19.13.35 (Linux; U; Android 14 gzip)' } else if (newClient === 'ANDROID_MUSIC') { ytContext.client.clientName = 'ANDROID_MUSIC' - ytContext.client.clientVersion = '6.37.50' - ytContext.client.userAgent = 'com.google.android.apps.youtube.music/6.37.50 (Linux; U; Android 14 gzip)' + ytContext.client.clientVersion = '6.44.54' + ytContext.client.userAgent = 'com.google.android.apps.youtube.music/6.44.54 (Linux; U; Android 14 gzip)' } } @@ -144,7 +151,8 @@ async function search(query, type, shouldLog) { ...(config.search.sources.youtube.authentication.enabled ? { Authorization: config.search.sources.youtube.authentication.authorization, Cookie: `SID=${config.search.sources.youtube.authentication.cookies.SID}; LOGIN_INFO=${config.search.sources.youtube.authentication.cookies.LOGIN_INFO}` - } : {}) + } : {}), + 'X-GOOG-API-FORMAT-VERSION': 2 }, body: { context: ytContext, @@ -263,7 +271,8 @@ async function loadFrom(query, type) { ...(config.search.sources.youtube.authentication.enabled ? { Authorization: config.search.sources.youtube.authentication.authorization, Cookie: `SID=${config.search.sources.youtube.authentication.cookies.SID}; LOGIN_INFO=${config.search.sources.youtube.authentication.cookies.LOGIN_INFO}` - } : {}) + } : {}), + 'X-GOOG-API-FORMAT-VERSION': 2 }, body: { context: ytContext, @@ -327,7 +336,8 @@ async function loadFrom(query, type) { ...(config.search.sources.youtube.authentication.enabled ? { Authorization: config.search.sources.youtube.authentication.authorization, Cookie: `SID=${config.search.sources.youtube.authentication.cookies.SID}; LOGIN_INFO=${config.search.sources.youtube.authentication.cookies.LOGIN_INFO}` - } : {}) + } : {}), + 'X-GOOG-API-FORMAT-VERSION': 2 }, body: { context: ytContext, @@ -439,7 +449,8 @@ async function loadFrom(query, type) { ...(config.search.sources.youtube.authentication.enabled ? { Authorization: config.search.sources.youtube.authentication.authorization, Cookie: `SID=${config.search.sources.youtube.authentication.cookies.SID}; LOGIN_INFO=${config.search.sources.youtube.authentication.cookies.LOGIN_INFO}` - } : {}) + } : {}), + 'X-GOOG-API-FORMAT-VERSION': 2 }, body: { context: ytContext, @@ -512,7 +523,8 @@ async function retrieveStream(identifier, type, title) { ...(config.search.sources.youtube.authentication.enabled ? { Authorization: config.search.sources.youtube.authentication.authorization, Cookie: `SID=${config.search.sources.youtube.authentication.cookies.SID}; LOGIN_INFO=${config.search.sources.youtube.authentication.cookies.LOGIN_INFO}` - } : {}) + } : {}), + 'X-GOOG-API-FORMAT-VERSION': 2 }, body: { context: ytContext, @@ -587,7 +599,8 @@ function loadLyrics(decodedTrack, language) { ...(config.search.sources.youtube.authentication.enabled ? { Authorization: config.search.sources.youtube.authentication.authorization, Cookie: `SID=${config.search.sources.youtube.authentication.cookies.SID}; LOGIN_INFO=${config.search.sources.youtube.authentication.cookies.LOGIN_INFO}` - } : {}) + } : {}), + 'X-GOOG-API-FORMAT-VERSION': 2 }, body: { context: ytContext, @@ -710,7 +723,10 @@ async function loadStream(url) { const stream = new PassThrough() await loadHLSPlaylist(url, stream) - resolve(stream) + resolve({ + stream, + bitrate: -1 + }) }) } diff --git a/src/utils.js b/src/utils.js index 6b84338..4b11306 100644 --- a/src/utils.js +++ b/src/utils.js @@ -872,6 +872,10 @@ export function loadHLS(url, stream, onceEnded) { return new Promise(async (resolve) => { const response = await http1makeRequest(url, { method: 'GET' }) const body = response.body.split('\n') + const metadata = { + bitrate: null + } + let nextLength = 0 body.nForEach(async (line, i) => { return new Promise(async (resolveSegment) => { @@ -884,6 +888,9 @@ export function loadHLS(url, stream, onceEnded) { if (line.startsWith('#')) { const tag = line.split(':')[0] + if (tag === '#EXTINF') + nextLength = parseFloat(line.split(':')[1].split(',')[0]) + if (tag === '#EXT-X-ENDLIST') { stream.end() @@ -895,11 +902,17 @@ export function loadHLS(url, stream, onceEnded) { const segment = await http1makeRequest(line, { method: 'GET', streamOnly: true }) + if (metadata.bitrate === null && segment.headers['content-length'] && nextLength) + metadata.bitrate = Math.round((parseInt(segment.headers['content-length'] * 8) / nextLength)) + + if (!onceEnded) + resolve(metadata) + segment.stream.on('data', (chunk) => stream.write(chunk)) segment.stream.on('end', () => { if (onceEnded && i === body.length - 2) { - resolve(true) + resolve(metadata) segment.stream.destroy() } else { @@ -910,8 +923,6 @@ export function loadHLS(url, stream, onceEnded) { }) }) }) - - if (!onceEnded) resolve(true) }) }