YT: auto select best matching resolution for used monitor

This commit is contained in:
Rafał Dzięgiel
2021-04-16 09:47:43 +02:00
parent b02f54a3a6
commit 3a998fb91e
4 changed files with 132 additions and 31 deletions

View File

@@ -19,6 +19,7 @@ class ClapperPlayer extends PlayerBase
this.needsFastSeekRestore = false; this.needsFastSeekRestore = false;
this.customVideoTitle = null; this.customVideoTitle = null;
this.windowMapped = false;
this.canAutoFullscreen = false; this.canAutoFullscreen = false;
this.playOnFullscreen = false; this.playOnFullscreen = false;
this.quitOnStop = false; this.quitOnStop = false;
@@ -54,7 +55,11 @@ class ClapperPlayer extends PlayerBase
if(!this.ytClient) if(!this.ytClient)
this.ytClient = new YouTube.YouTubeClient(); this.ytClient = new YouTube.YouTubeClient();
this.ytClient.getPlaybackDataAsync(videoId) const { root } = this.widget;
const surface = root.get_surface();
const monitor = root.display.get_monitor_at_surface(surface);
this.ytClient.getPlaybackDataAsync(videoId, monitor)
.then(data => { .then(data => {
this.customVideoTitle = data.title; this.customVideoTitle = data.title;
super.set_uri(data.uri); super.set_uri(data.uri);
@@ -121,10 +126,9 @@ class ClapperPlayer extends PlayerBase
this.playlistWidget.addItem(uri); this.playlistWidget.addItem(uri);
} }
const firstTrack = this.playlistWidget.get_row_at_index(0); /* If not mapped yet, first track will play after map */
if(!firstTrack) return; if(this.windowMapped)
this._playFirstTrack();
firstTrack.activate();
} }
set_subtitles(source) set_subtitles(source)
@@ -291,6 +295,14 @@ class ClapperPlayer extends PlayerBase
: Gst.filename_to_uri(source); : Gst.filename_to_uri(source);
} }
_playFirstTrack()
{
const firstTrack = this.playlistWidget.get_row_at_index(0);
if(!firstTrack) return;
firstTrack.activate();
}
_performCloseCleanup(window) _performCloseCleanup(window)
{ {
window.disconnect(this.closeRequestSignal); window.disconnect(this.closeRequestSignal);
@@ -522,6 +534,12 @@ class ClapperPlayer extends PlayerBase
} }
} }
_onWindowMap(window)
{
this.windowMapped = true;
this._playFirstTrack();
}
_onCloseRequest(window) _onCloseRequest(window)
{ {
this._performCloseCleanup(window); this._performCloseCleanup(window);

View File

@@ -578,6 +578,8 @@ class ClapperWidget extends Gtk.Grid
surface.connect('notify::state', this._onStateNotify.bind(this)); surface.connect('notify::state', this._onStateNotify.bind(this));
surface.connect('layout', this._onLayoutUpdate.bind(this)); surface.connect('layout', this._onLayoutUpdate.bind(this));
this.player._onWindowMap(window);
} }
_clearTimeout(name) _clearTimeout(name)

View File

@@ -3,6 +3,7 @@ const Dash = imports.src.dash;
const Debug = imports.src.debug; const Debug = imports.src.debug;
const FileOps = imports.src.fileOps; const FileOps = imports.src.fileOps;
const Misc = imports.src.misc; const Misc = imports.src.misc;
const YTItags = imports.src.youtubeItags;
const YTDL = imports.src.assets['node-ytdl-core']; const YTDL = imports.src.assets['node-ytdl-core'];
const debug = Debug.ytDebug; const debug = Debug.ytDebug;
@@ -304,7 +305,7 @@ var YouTubeClient = GObject.registerClass({
}); });
} }
async getPlaybackDataAsync(videoId) async getPlaybackDataAsync(videoId, monitor)
{ {
const info = await this.getVideoInfoPromise(videoId).catch(debug); const info = await this.getVideoInfoPromise(videoId).catch(debug);
@@ -312,7 +313,13 @@ var YouTubeClient = GObject.registerClass({
throw new Error('no YouTube video info'); throw new Error('no YouTube video info');
let uri = null; let uri = null;
const dashInfo = await this.getDashInfoAsync(info).catch(debug); const itagOpts = {
width: monitor.geometry.width * monitor.scale_factor,
height: monitor.geometry.height * monitor.scale_factor,
codec: 'h264',
types: ['standard', 'hfr'],
};
const dashInfo = await this.getDashInfoAsync(info, itagOpts).catch(debug);
if(dashInfo) { if(dashInfo) {
debug('parsed video info to dash info'); debug('parsed video info to dash info');
@@ -333,7 +340,7 @@ var YouTubeClient = GObject.registerClass({
} }
if(!uri) if(!uri)
uri = this.getBestCombinedUri(info); uri = this.getBestCombinedUri(info, itagOpts);
if(!uri) if(!uri)
throw new Error('no YouTube video URI'); throw new Error('no YouTube video URI');
@@ -349,7 +356,7 @@ var YouTubeClient = GObject.registerClass({
return { uri, title }; return { uri, title };
} }
async getDashInfoAsync(info) async getDashInfoAsync(info, itagOpts)
{ {
if( if(
!info.streamingData !info.streamingData
@@ -360,20 +367,10 @@ 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');
const allowedFormats = {
video: [ debug(`obtaining dash itags for resolution: ${itagOpts.width}x${itagOpts.height}`);
133, const dashItags = YTItags.getDashItags(itagOpts);
134, debug(`dash itags: ${JSON.stringify(dashItags)}`);
135,
136,
137,
298,
299,
],
audio: [
140,
]
};
const filteredStreams = { const filteredStreams = {
video: [], video: [],
@@ -382,11 +379,11 @@ var YouTubeClient = GObject.registerClass({
for(let fmt of ['video', 'audio']) { for(let fmt of ['video', 'audio']) {
debug(`filtering ${fmt} streams`); debug(`filtering ${fmt} streams`);
let index = allowedFormats[fmt].length; let index = dashItags[fmt].length;
while(index--) { while(index--) {
const itag = allowedFormats[fmt][index]; const itag = dashItags[fmt][index];
const foundStream = info.streamingData.adaptiveFormats.find(stream => (stream.itag == itag)); const foundStream = info.streamingData.adaptiveFormats.find(stream => stream.itag == itag);
if(foundStream) { if(foundStream) {
/* Parse and convert mimeType string into object */ /* Parse and convert mimeType string into object */
foundStream.mimeInfo = this._getMimeInfo(foundStream.mimeType); foundStream.mimeInfo = this._getMimeInfo(foundStream.mimeType);
@@ -440,16 +437,33 @@ var YouTubeClient = GObject.registerClass({
}; };
} }
getBestCombinedUri(info) getBestCombinedUri(info, itagOpts)
{ {
debug('obtaining best combined URL'); debug(`obtaining best combined URL for resolution: ${itagOpts.width}x${itagOpts.height}`);
if(!info.streamingData.formats.length) if(!info.streamingData.formats.length)
return null; return null;
const combinedStream = info.streamingData.formats[ let combinedStream;
const combinedItags = YTItags.getCombinedItags(itagOpts);
let index = combinedItags.length;
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) {
debug('trying any combined stream as last resort');
combinedStream = info.streamingData.formats[
info.streamingData.formats.length - 1 info.streamingData.formats.length - 1
]; ];
}
if(!combinedStream || !combinedStream.url) if(!combinedStream || !combinedStream.url)
return null; return null;

67
src/youtubeItags.js Normal file
View File

@@ -0,0 +1,67 @@
const Itags = {
video: {
h264: {
standard: {
240: 133,
360: 134,
480: 135,
720: 136,
1080: 137,
},
hfr: {
720: 298,
1080: 299,
},
},
},
audio: {
aac: [140],
opus: [249, 250, 251],
},
combined: {
360: 18,
720: 22,
}
};
function _appendItagArray(arr, opts, formats)
{
const keys = Object.keys(formats);
for(let fmt of keys) {
arr.push(formats[fmt]);
if(
fmt >= opts.height
|| Math.floor(fmt * 16 / 9) >= opts.width
)
break;
}
return arr;
}
function getDashItags(opts)
{
const allowed = {
video: [],
audio: (opts.codec === 'h264')
? Itags.audio.aac
: Itags.audio.opus
};
for(let type of opts.types) {
const formats = Itags.video[opts.codec][type];
_appendItagArray(allowed.video, opts, formats);
}
return allowed;
}
function getCombinedItags(opts)
{
const arr = [];
_appendItagArray(arr, opts, Itags.combined);
return arr;
}