YT: resolve redirects on the Clapper side

Instead of providing URIs directly to GStreamer, follow redirects and provide that final URI. With this change souphttpsrc will not have to go through redirects from the beginning for each video segment.
This commit is contained in:
Rafostar
2021-04-11 14:46:08 +02:00
parent b5711b145b
commit 7cf86e92eb
3 changed files with 84 additions and 36 deletions

View File

@@ -4,29 +4,11 @@ const Misc = imports.src.misc;
const { debug } = Debug;
function generateDash(info)
function generateDash(dashInfo)
{
if(
!info.streamingData
|| !info.streamingData.adaptiveFormats
|| !info.streamingData.adaptiveFormats.length
)
return null;
debug('generating dash');
/* TODO: Options in prefs to set preferred video formats for adaptive streaming */
const videoStream = info.streamingData.adaptiveFormats.find(stream => {
return (stream.mimeType.startsWith('video/mp4') && stream.quality === 'hd1080');
});
const audioStream = info.streamingData.adaptiveFormats.find(stream => {
return (stream.mimeType.startsWith('audio/mp4'));
});
if(!videoStream || !audioStream)
return null;
const bufferSec = Math.min(4, info.videoDetails.lengthSeconds);
const bufferSec = Math.min(4, dashInfo.duration);
const dash = [
`<?xml version="1.0" encoding="UTF-8"?>`,
@@ -34,19 +16,23 @@ function generateDash(info)
` xmlns="urn:mpeg:dash:schema:mpd:2011"`,
` xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd"`,
` type="static"`,
` mediaPresentationDuration="PT${info.videoDetails.lengthSeconds}S"`,
` mediaPresentationDuration="PT${dashInfo.duration}S"`,
` minBufferTime="PT${bufferSec}S"`,
` profiles="urn:mpeg:dash:profile:isoff-on-demand:2011">`,
` <Period>`,
_addAdaptationSet([videoStream]),
_addAdaptationSet([audioStream]),
` <Period>`
];
for(let adaptation of dashInfo.adaptations)
dash.push(_addAdaptationSet(adaptation));
dash.push(
` </Period>`,
`</MPD>`
].join('\n');
);
debug('dash generated');
return dash;
return dash.join('\n');
}
function _addAdaptationSet(streamsArr)

View File

@@ -86,16 +86,23 @@ class ClapperPlayer extends PlayerBase
if(!info)
throw new Error('no YouTube video info');
const dash = Dash.generateDash(info);
let videoUri = null;
const dashInfo = await this.ytClient.getDashInfoAsync(info).catch(debug);
if(dash) {
const dashFile = await FileOps.saveFilePromise(
'tmp', null, 'clapper.mpd', dash
).catch(debug);
if(dashInfo) {
debug('parsed video info to dash info');
const dash = Dash.generateDash(dashInfo);
if(dashFile)
videoUri = dashFile.get_uri();
if(dash) {
debug('got dash');
const dashFile = await FileOps.saveFilePromise(
'tmp', null, 'clapper.mpd', dash
).catch(debug);
if(dashFile)
videoUri = dashFile.get_uri();
}
}
if(!videoUri)

View File

@@ -23,7 +23,7 @@ var YouTubeClient = GObject.registerClass({
_init()
{
super._init({
timeout: 5,
timeout: 7,
max_conns_per_host: 1,
/* TODO: share this with GstClapper lib (define only once) */
user_agent: 'Mozilla/5.0 (X11; Linux x86_64; rv:86.0) Gecko/20100101 Firefox/86.0',
@@ -190,7 +190,10 @@ var YouTubeClient = GObject.registerClass({
debug(`found player URI: ${ytUri}`);
const ytId = ytPath.split('/').find(el => Misc.isHex(el));
let ytSigData = await FileOps.getFileContentsPromise('user_cache', 'yt-sig', ytId).catch(debug);
let ytSigData = await FileOps.getFileContentsPromise(
'user_cache', 'yt-sig', ytId
).catch(debug);
if(ytSigData) {
ytSigData = ytSigData.split(';');
@@ -299,8 +302,53 @@ var YouTubeClient = GObject.registerClass({
});
}
async getDashInfoAsync(info)
{
if(
!info.streamingData
|| !info.streamingData.adaptiveFormats
|| !info.streamingData.adaptiveFormats.length
)
return null;
/* TODO: Options in prefs to set preferred video formats for adaptive streaming */
const videoStream = info.streamingData.adaptiveFormats.find(stream => {
return (stream.mimeType.startsWith('video/mp4') && stream.quality === 'hd1080');
});
const audioStream = info.streamingData.adaptiveFormats.find(stream => {
return (stream.mimeType.startsWith('audio/mp4'));
});
if(!videoStream || !audioStream)
return null;
debug('following redirects');
for(let stream of [videoStream, audioStream]) {
debug(`initial URL: ${stream.url}`);
const result = await this._downloadDataPromise(stream.url, 'HEAD').catch(debug);
if(!result) return null;
stream.url = result.uri;
debug(`resolved URL: ${stream.url}`);
}
debug('all redirects resolved');
return {
duration: info.videoDetails.lengthSeconds,
adaptations: [
[videoStream],
[audioStream],
]
};
}
getBestCombinedUri(info)
{
debug('obtaining best combined URL');
if(!info.streamingData.formats.length)
return null;
@@ -332,11 +380,14 @@ var YouTubeClient = GObject.registerClass({
_downloadDataPromise(url, method, reqData)
{
method = method || 'GET';
return new Promise((resolve, reject) => {
const message = Soup.Message.new(method || 'GET', url);
const message = Soup.Message.new(method, url);
const result = {
data: null,
isAborted: false,
uri: null,
};
if(reqData) {
@@ -353,6 +404,10 @@ var YouTubeClient = GObject.registerClass({
if(statusCode === 200) {
result.data = msg.response_body.data;
if(method === 'HEAD')
result.uri = msg.uri.to_string(false);
return resolve(result);
}