mirror of
https://github.com/Rafostar/clapper.git
synced 2025-08-30 16:02:00 +02:00
YT: support non-adaptive live streaming
This commit is contained in:
113
src/youtube.js
113
src/youtube.js
@@ -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)
|
||||||
|
@@ -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);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user