diff --git a/src/player.js b/src/player.js index 010fd942..261a937f 100644 --- a/src/player.js +++ b/src/player.js @@ -19,6 +19,7 @@ class ClapperPlayer extends PlayerBase this.needsFastSeekRestore = false; this.customVideoTitle = null; + this.windowMapped = false; this.canAutoFullscreen = false; this.playOnFullscreen = false; this.quitOnStop = false; @@ -54,7 +55,11 @@ class ClapperPlayer extends PlayerBase if(!this.ytClient) 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 => { this.customVideoTitle = data.title; super.set_uri(data.uri); @@ -121,10 +126,9 @@ class ClapperPlayer extends PlayerBase this.playlistWidget.addItem(uri); } - const firstTrack = this.playlistWidget.get_row_at_index(0); - if(!firstTrack) return; - - firstTrack.activate(); + /* If not mapped yet, first track will play after map */ + if(this.windowMapped) + this._playFirstTrack(); } set_subtitles(source) @@ -291,6 +295,14 @@ class ClapperPlayer extends PlayerBase : Gst.filename_to_uri(source); } + _playFirstTrack() + { + const firstTrack = this.playlistWidget.get_row_at_index(0); + if(!firstTrack) return; + + firstTrack.activate(); + } + _performCloseCleanup(window) { window.disconnect(this.closeRequestSignal); @@ -522,6 +534,12 @@ class ClapperPlayer extends PlayerBase } } + _onWindowMap(window) + { + this.windowMapped = true; + this._playFirstTrack(); + } + _onCloseRequest(window) { this._performCloseCleanup(window); diff --git a/src/widget.js b/src/widget.js index 6f1fe549..f32ea72e 100644 --- a/src/widget.js +++ b/src/widget.js @@ -578,6 +578,8 @@ class ClapperWidget extends Gtk.Grid surface.connect('notify::state', this._onStateNotify.bind(this)); surface.connect('layout', this._onLayoutUpdate.bind(this)); + + this.player._onWindowMap(window); } _clearTimeout(name) diff --git a/src/youtube.js b/src/youtube.js index 325e9262..a1be9294 100644 --- a/src/youtube.js +++ b/src/youtube.js @@ -3,6 +3,7 @@ const Dash = imports.src.dash; const Debug = imports.src.debug; const FileOps = imports.src.fileOps; const Misc = imports.src.misc; +const YTItags = imports.src.youtubeItags; const YTDL = imports.src.assets['node-ytdl-core']; 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); @@ -312,7 +313,13 @@ var YouTubeClient = GObject.registerClass({ throw new Error('no YouTube video info'); 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) { debug('parsed video info to dash info'); @@ -333,7 +340,7 @@ var YouTubeClient = GObject.registerClass({ } if(!uri) - uri = this.getBestCombinedUri(info); + uri = this.getBestCombinedUri(info, itagOpts); if(!uri) throw new Error('no YouTube video URI'); @@ -349,7 +356,7 @@ var YouTubeClient = GObject.registerClass({ return { uri, title }; } - async getDashInfoAsync(info) + async getDashInfoAsync(info, itagOpts) { if( !info.streamingData @@ -360,20 +367,10 @@ var YouTubeClient = GObject.registerClass({ /* TODO: Options in prefs to set preferred video formats and adaptive streaming */ const isAdaptiveEnabled = settings.get_boolean('yt-adaptive-enabled'); - const allowedFormats = { - video: [ - 133, - 134, - 135, - 136, - 137, - 298, - 299, - ], - audio: [ - 140, - ] - }; + + debug(`obtaining dash itags for resolution: ${itagOpts.width}x${itagOpts.height}`); + const dashItags = YTItags.getDashItags(itagOpts); + debug(`dash itags: ${JSON.stringify(dashItags)}`); const filteredStreams = { video: [], @@ -382,11 +379,11 @@ var YouTubeClient = GObject.registerClass({ for(let fmt of ['video', 'audio']) { debug(`filtering ${fmt} streams`); - let index = allowedFormats[fmt].length; + let index = dashItags[fmt].length; while(index--) { - const itag = allowedFormats[fmt][index]; - const foundStream = info.streamingData.adaptiveFormats.find(stream => (stream.itag == itag)); + const itag = dashItags[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); @@ -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) return null; - const combinedStream = info.streamingData.formats[ - info.streamingData.formats.length - 1 - ]; + 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 + ]; + } if(!combinedStream || !combinedStream.url) return null; diff --git a/src/youtubeItags.js b/src/youtubeItags.js new file mode 100644 index 00000000..2f82d8bd --- /dev/null +++ b/src/youtubeItags.js @@ -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; +}