mirror of
https://github.com/Rafostar/clapper.git
synced 2025-08-29 23:32:04 +02:00
YT: try harder to find suitable DASH streams
Instead of searching for 1080p only, accept also other H.264 formats for DASH streaming
This commit is contained in:
31
src/dash.js
31
src/dash.js
@@ -37,7 +37,9 @@ function generateDash(dashInfo)
|
|||||||
|
|
||||||
function _addAdaptationSet(streamsArr)
|
function _addAdaptationSet(streamsArr)
|
||||||
{
|
{
|
||||||
const mimeInfo = _getMimeInfo(streamsArr[0].mimeType);
|
/* We just need it for adaptation type,
|
||||||
|
* so any stream will do */
|
||||||
|
const { mimeInfo } = streamsArr[0];
|
||||||
|
|
||||||
const adaptArr = [
|
const adaptArr = [
|
||||||
`contentType="${mimeInfo.content}"`,
|
`contentType="${mimeInfo.content}"`,
|
||||||
@@ -93,11 +95,9 @@ function _addAdaptationSet(streamsArr)
|
|||||||
|
|
||||||
function _getStreamRepresentation(stream)
|
function _getStreamRepresentation(stream)
|
||||||
{
|
{
|
||||||
const mimeInfo = _getMimeInfo(stream.mimeType);
|
|
||||||
|
|
||||||
const repOptsArr = [
|
const repOptsArr = [
|
||||||
`id="${stream.itag}"`,
|
`id="${stream.itag}"`,
|
||||||
`codecs="${mimeInfo.codecs}"`,
|
`codecs="${stream.mimeInfo.codecs}"`,
|
||||||
`bandwidth="${stream.bitrate}"`,
|
`bandwidth="${stream.bitrate}"`,
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -120,13 +120,8 @@ function _getStreamRepresentation(stream)
|
|||||||
repArr.push(` <AudioChannelConfiguration ${audioConfArr.join(' ')}/>`);
|
repArr.push(` <AudioChannelConfiguration ${audioConfArr.join(' ')}/>`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const encodedURL = Misc.encodeHTML(stream.url)
|
|
||||||
.replace('?', '/')
|
|
||||||
.replace(/&/g, '/')
|
|
||||||
.replace(/=/g, '/');
|
|
||||||
|
|
||||||
repArr.push(
|
repArr.push(
|
||||||
` <BaseURL>${encodedURL}</BaseURL>`
|
` <BaseURL>${stream.url}</BaseURL>`
|
||||||
);
|
);
|
||||||
|
|
||||||
if(stream.indexRange) {
|
if(stream.indexRange) {
|
||||||
@@ -152,22 +147,6 @@ function _getStreamRepresentation(stream)
|
|||||||
return repArr.join('\n');
|
return repArr.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
function _getMimeInfo(mimeType)
|
|
||||||
{
|
|
||||||
const mimeArr = mimeType.split(';');
|
|
||||||
|
|
||||||
let codecs = mimeArr.find(info => info.includes('codecs')).split('=')[1];
|
|
||||||
codecs = codecs.substring(1, codecs.length - 1);
|
|
||||||
|
|
||||||
const mimeInfo = {
|
|
||||||
content: mimeArr[0].split('/')[0],
|
|
||||||
type: mimeArr[0],
|
|
||||||
codecs,
|
|
||||||
};
|
|
||||||
|
|
||||||
return mimeInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
function _getPar(width, height)
|
function _getPar(width, height)
|
||||||
{
|
{
|
||||||
const gcd = _getGCD(width, height);
|
const gcd = _getGCD(width, height);
|
||||||
|
102
src/youtube.js
102
src/youtube.js
@@ -357,27 +357,75 @@ var YouTubeClient = GObject.registerClass({
|
|||||||
)
|
)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
/* TODO: Options in prefs to set preferred video formats for adaptive streaming */
|
/* TODO: Options in prefs to set preferred video formats and adaptive streaming */
|
||||||
const videoStream = info.streamingData.adaptiveFormats.find(stream => {
|
const isAdaptiveEnabled = false;
|
||||||
return (stream.mimeType.startsWith('video/mp4') && stream.quality === 'hd1080');
|
const allowedFormats = {
|
||||||
});
|
video: [
|
||||||
const audioStream = info.streamingData.adaptiveFormats.find(stream => {
|
133,
|
||||||
return (stream.mimeType.startsWith('audio/mp4'));
|
134,
|
||||||
});
|
135,
|
||||||
|
136,
|
||||||
|
137,
|
||||||
|
298,
|
||||||
|
299,
|
||||||
|
],
|
||||||
|
audio: [
|
||||||
|
140,
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
if(!videoStream || !audioStream)
|
const filteredStreams = {
|
||||||
return null;
|
video: [],
|
||||||
|
audio: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
for(let fmt of ['video', 'audio']) {
|
||||||
|
debug(`filtering ${fmt} streams`);
|
||||||
|
let index = allowedFormats[fmt].length;
|
||||||
|
|
||||||
|
while(index--) {
|
||||||
|
const itag = allowedFormats[fmt][index];
|
||||||
|
const foundStream = info.streamingData.adaptiveFormats.find(stream => (stream.itag == itag));
|
||||||
|
if(foundStream) {
|
||||||
|
/* Parse and convert mimeType string into object */
|
||||||
|
foundStream.mimeInfo = this._getMimeInfo(foundStream.mimeType);
|
||||||
|
|
||||||
|
/* Sanity check */
|
||||||
|
if(!foundStream.mimeInfo || foundStream.mimeInfo.content !== fmt) {
|
||||||
|
debug(new Error(`mimeType parsing failed on stream: ${itag}`));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sort from worst to best */
|
||||||
|
filteredStreams[fmt].unshift(foundStream);
|
||||||
|
debug(`added ${fmt} itag: ${foundStream.itag}`);
|
||||||
|
|
||||||
|
if(!isAdaptiveEnabled)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!filteredStreams[fmt].length) {
|
||||||
|
debug(`dash info ${fmt} streams list is empty`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
debug('following redirects');
|
debug('following redirects');
|
||||||
|
|
||||||
for(let stream of [videoStream, audioStream]) {
|
for(let fmtArr of Object.values(filteredStreams)) {
|
||||||
debug(`initial URL: ${stream.url}`);
|
for(let stream of fmtArr) {
|
||||||
|
debug(`initial URL: ${stream.url}`);
|
||||||
|
|
||||||
const result = await this._downloadDataPromise(stream.url, 'HEAD').catch(debug);
|
const result = await this._downloadDataPromise(stream.url, 'HEAD').catch(debug);
|
||||||
if(!result) return null;
|
if(!result) return null;
|
||||||
|
|
||||||
stream.url = result.uri;
|
stream.url = Misc.encodeHTML(result.uri)
|
||||||
debug(`resolved URL: ${stream.url}`);
|
.replace('?', '/')
|
||||||
|
.replace(/&/g, '/')
|
||||||
|
.replace(/=/g, '/');
|
||||||
|
|
||||||
|
debug(`resolved URL: ${stream.url}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
debug('all redirects resolved');
|
debug('all redirects resolved');
|
||||||
@@ -385,8 +433,8 @@ var YouTubeClient = GObject.registerClass({
|
|||||||
return {
|
return {
|
||||||
duration: info.videoDetails.lengthSeconds,
|
duration: info.videoDetails.lengthSeconds,
|
||||||
adaptations: [
|
adaptations: [
|
||||||
[videoStream],
|
filteredStreams.video,
|
||||||
[audioStream],
|
filteredStreams.audio,
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -527,6 +575,26 @@ var YouTubeClient = GObject.registerClass({
|
|||||||
return reduced;
|
return reduced;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_getMimeInfo(mimeType)
|
||||||
|
{
|
||||||
|
debug(`parsing mimeType: ${mimeType}`);
|
||||||
|
|
||||||
|
const mimeArr = mimeType.split(';');
|
||||||
|
|
||||||
|
let codecs = mimeArr.find(info => info.includes('codecs')).split('=')[1];
|
||||||
|
codecs = codecs.substring(1, codecs.length - 1);
|
||||||
|
|
||||||
|
const mimeInfo = {
|
||||||
|
content: mimeArr[0].split('/')[0],
|
||||||
|
type: mimeArr[0],
|
||||||
|
codecs,
|
||||||
|
};
|
||||||
|
|
||||||
|
debug(`parsed mimeType: ${JSON.stringify(mimeInfo)}`);
|
||||||
|
|
||||||
|
return mimeInfo;
|
||||||
|
}
|
||||||
|
|
||||||
_getPlayerInfoPromise(videoId)
|
_getPlayerInfoPromise(videoId)
|
||||||
{
|
{
|
||||||
const data = this._getPlayerPostData(videoId);
|
const data = this._getPlayerPostData(videoId);
|
||||||
|
Reference in New Issue
Block a user