YT: support non-adaptive live streaming

This commit is contained in:
Rafał Dzięgiel
2021-04-17 18:02:33 +02:00
parent 62b6de6db2
commit ab8cafa0b8
2 changed files with 88 additions and 44 deletions

View File

@@ -320,7 +320,7 @@ var YouTubeClient = GObject.registerClass({
type: settings.get_string('yt-quality-type'), type: settings.get_string('yt-quality-type'),
}; };
uri = this.getHLSUri(info, itagOpts); uri = await this.getHLSUriAsync(info, itagOpts);
if(!uri) { if(!uri) {
const dashInfo = await this.getDashInfoAsync(info, itagOpts).catch(debug); const dashInfo = await this.getDashInfoAsync(info, itagOpts).catch(debug);
@@ -361,6 +361,55 @@ var YouTubeClient = GObject.registerClass({
return { uri, title }; return { uri, title };
} }
async getHLSUriAsync(info, itagOpts)
{
const isLive = info.videoDetails.isLiveContent;
debug(`video is live: ${isLive}`);
/* YouTube only uses HLS for live content */
if(!isLive)
return null;
const hlsUri = info.streamingData.hlsManifestUrl;
if(!hlsUri) {
debug(new Error('no HLS manifest URL'));
return null;
}
if(!settings.get_boolean('yt-adaptive-enabled')) {
const result = await this._downloadDataPromise(hlsUri).catch(debug);
if(!result || !result.data) {
debug(new Error('HLS manifest download failed'));
return hlsUri;
}
const hlsArr = result.data.split('\n');
const hlsStreams = [];
let index = hlsArr.length;
while(index--) {
const url = hlsArr[index];
if(!Gst.Uri.is_valid(url))
continue;
const itagIndex = url.indexOf('/itag/') + 6;
const itag = url.substring(itagIndex, itagIndex + 2);
hlsStreams.push({ itag, url });
}
debug(`obtaining HLS itags for resolution: ${itagOpts.width}x${itagOpts.height}`);
const hlsItags = YTItags.getHLSItags(itagOpts);
debug(`HLS itags: ${JSON.stringify(hlsItags)}`);
const hlsStream = this.getBestStreamFromItags(hlsStreams, hlsItags);
if(hlsStream)
return hlsStream.url;
}
return hlsUri;
}
async getDashInfoAsync(info, itagOpts) async getDashInfoAsync(info, itagOpts)
{ {
if( if(
@@ -373,9 +422,9 @@ var YouTubeClient = GObject.registerClass({
/* TODO: Options in prefs to set preferred video formats and adaptive streaming */ /* TODO: Options in prefs to set preferred video formats and adaptive streaming */
const isAdaptiveEnabled = settings.get_boolean('yt-adaptive-enabled'); const isAdaptiveEnabled = settings.get_boolean('yt-adaptive-enabled');
debug(`obtaining dash itags for resolution: ${itagOpts.width}x${itagOpts.height}`); debug(`obtaining DASH itags for resolution: ${itagOpts.width}x${itagOpts.height}`);
const dashItags = YTItags.getDashItags(itagOpts); const dashItags = YTItags.getDashItags(itagOpts);
debug(`dash itags: ${JSON.stringify(dashItags)}`); debug(`DASH itags: ${JSON.stringify(dashItags)}`);
const filteredStreams = { const filteredStreams = {
video: [], video: [],
@@ -442,53 +491,20 @@ var YouTubeClient = GObject.registerClass({
}; };
} }
getHLSUri(info, itagOpts)
{
const isLive = info.videoDetails.isLiveContent;
debug(`video is live: ${isLive}`);
/* YouTube only uses HLS for live content */
if(!isLive)
return null;
const hlsUri = info.streamingData.hlsManifestUrl;
if(!hlsUri) {
debug(new Error('no HLS manifest URL'));
return null;
}
/* TODO: download manifest and select best resolution
* for monitor when adaptive streaming is disabled */
return hlsUri;
}
getBestCombinedUri(info, itagOpts) getBestCombinedUri(info, itagOpts)
{ {
debug(`obtaining best combined URL for resolution: ${itagOpts.width}x${itagOpts.height}`); debug(`obtaining best combined URL for resolution: ${itagOpts.width}x${itagOpts.height}`);
const streams = info.streamingData.formats;
if(!info.streamingData.formats.length) if(!streams.length)
return null; return null;
let combinedStream;
const combinedItags = YTItags.getCombinedItags(itagOpts); const combinedItags = YTItags.getCombinedItags(itagOpts);
let index = combinedItags.length; let combinedStream = this.getBestStreamFromItags(streams, combinedItags);
while(index--) {
const itag = combinedItags[index];
combinedStream = info.streamingData.formats.find(stream => stream.itag == itag);
if(combinedStream) {
debug(`found best combined itag: ${combinedStream.itag}`);
break;
}
}
if(!combinedStream) { if(!combinedStream) {
debug('trying any combined stream as last resort'); debug('trying any combined stream as last resort');
combinedStream = info.streamingData.formats[ combinedStream = streams[streams.length - 1];
info.streamingData.formats.length - 1
];
} }
if(!combinedStream || !combinedStream.url) if(!combinedStream || !combinedStream.url)
@@ -497,6 +513,23 @@ var YouTubeClient = GObject.registerClass({
return combinedStream.url; return combinedStream.url;
} }
getBestStreamFromItags(streams, itags)
{
let index = itags.length;
while(index--) {
const itag = itags[index];
const stream = streams.find(stream => stream.itag == itag);
if(stream) {
debug(`found preferred stream itag: ${stream.itag}`);
return stream;
}
}
debug('could not find preferred stream for itags');
return null;
}
compareLastVideoId(videoId) compareLastVideoId(videoId)
{ {
if(!this.lastInfo) if(!this.lastInfo)

View File

@@ -21,6 +21,13 @@ const Itags = {
combined: { combined: {
360: 18, 360: 18,
720: 22, 720: 22,
},
hls: {
240: 92,
360: 93,
480: 94,
720: 95,
1080: 96,
} }
}; };
@@ -37,6 +44,8 @@ function _appendItagArray(arr, opts, formats)
) )
break; break;
} }
return arr;
} }
function getDashItags(opts) function getDashItags(opts)
@@ -62,8 +71,10 @@ function getDashItags(opts)
function getCombinedItags(opts) function getCombinedItags(opts)
{ {
const arr = []; return _appendItagArray([], opts, Itags.combined);
_appendItagArray(arr, opts, Itags.combined); }
return arr; function getHLSItags(opts)
{
return _appendItagArray([], opts, Itags.hls);
} }