From 206da4871bef89a94c86b4bedd39b45da63097e3 Mon Sep 17 00:00:00 2001 From: Christian Ebert Date: Tue, 1 Oct 2024 15:01:19 +0100 Subject: [PATCH] feat(debug): check for presence of default controls component tree - also bail out immediately when default tree structure has changed at runtime - clean up and refactor debug logging. Implements #58 --- src/sprite-thumbnails.js | 65 ++++++++++++++++++++++++++-------- test/sprite-thumbnails.test.js | 44 ++++++++++++++++++++--- 2 files changed, 89 insertions(+), 20 deletions(-) diff --git a/src/sprite-thumbnails.js b/src/sprite-thumbnails.js index 7d48987..825725d 100644 --- a/src/sprite-thumbnails.js +++ b/src/sprite-thumbnails.js @@ -18,16 +18,39 @@ const spriteThumbs = (player, plugin, options) => { const dom = videojs.dom; const obj = videojs.obj; const log = plugin.log; + const debug = log.debug; - const controls = player.controlBar; + const defaultState = { ...plugin.state }; + const setDefaultState = () => { + plugin.setState(defaultState); + }; // default control bar component tree is expected // https://docs.videojs.com/tutorial-components.html#default-component-tree - const progress = controls && controls.progressControl; - const seekBar = progress && progress.seekBar; - const mouseTimeTooltip = seekBar && seekBar.mouseTimeDisplay && seekBar.mouseTimeDisplay.timeTooltip; - const tooltipEl = mouseTimeTooltip && mouseTimeTooltip.el(); - const tooltipStyleOrig = tooltipEl && tooltipEl.style; + const _controlBar = 'ControlBar'; + const _progressControl = 'ProgressControl'; + const _seekBar = 'SeekBar'; + const _timeTooltip = 'TimeTooltip'; + const playerDescendant = (componentName) => { + const descendants = [ + _controlBar, + _progressControl, + _seekBar, + 'MouseTimeDisplay', + _timeTooltip + ]; + const idx = descendants.indexOf(componentName); + const component = player.getDescendant(descendants.slice(0, idx + 1)); + + if (!component) { + setDefaultState(); + debug(`component tree ${descendants.join(' > ')} required`); + } + return component; + }; + + let tooltipEl; + let tooltipStyleOrig; const getUrl = idx => { const urlArray = options.urlArray; @@ -37,7 +60,12 @@ const spriteThumbs = (player, plugin, options) => { }; const hijackMouseTooltip = evt => { - const seekBarEl = seekBar.el(); + if (!playerDescendant(_timeTooltip)) { + return; + } + const seekBarEl = playerDescendant(_seekBar).el(); + const controlsTop = dom + .findPosition(playerDescendant(_controlBar).el()).top; const playerWidth = player.currentWidth(); const duration = player.duration(); const interval = options.interval; @@ -62,7 +90,6 @@ const spriteThumbs = (player, plugin, options) => { const scaledHeight = options.height * scaleFactor; const cleft = Math.floor(position % columns) * -scaledWidth; const ctop = Math.floor(position / columns) * -scaledHeight; - const controlsTop = dom.findPosition(controls.el()).top; const seekBarTop = dom.findPosition(seekBarEl).top; // top of seekBar is 0 position const topOffset = -scaledHeight - Math.max(0, seekBarTop - controlsTop); @@ -112,18 +139,19 @@ const spriteThumbs = (player, plugin, options) => { plugin.on('statechanged', () => { const pstate = plugin.state; const spriteEvents = ['mousemove', 'touchmove']; - const debug = log.debug; + const progress = playerDescendant(_progressControl); if (pstate.ready) { debug('ready to show thumbnails'); progress.on(spriteEvents, hijackMouseTooltip); } else { if (!options.url && !options.urlArray.length) { - log('no urls given'); + debug('no urls given, resetting'); + } + if (progress) { + progress.off(spriteEvents, hijackMouseTooltip); + tooltipEl.style = tooltipStyleOrig; } - debug('resetting'); - progress.off(spriteEvents, hijackMouseTooltip); - tooltipEl.style = tooltipStyleOrig; } player.toggleClass('vjs-thumbnails-ready', pstate.ready); }); @@ -137,6 +165,10 @@ const spriteThumbs = (player, plugin, options) => { // `spriteThumbnails` options cannot be merged // Thereafter the `loadstart` callback is redundant. player.off('loadstart', init); + + // clean slate + setDefaultState(); + // if present, merge source config with current config const plugName = plugin.name; const thumbSource = player.currentSources().find(source => { @@ -151,7 +183,6 @@ const spriteThumbs = (player, plugin, options) => { if (!Object.keys(srcOpts).length) { srcOpts = {url: '', urlArray: []}; - log('disabling plugin'); } else if (urlArray && urlArray.length) { srcOpts.url = ''; } else if (srcOpts.url) { @@ -160,12 +191,16 @@ const spriteThumbs = (player, plugin, options) => { plugin.options = options = obj.merge(options, srcOpts); } + const mouseTimeTooltip = playerDescendant(_timeTooltip); + if (!mouseTimeTooltip || evt.type === 'loadstart') { return; } + tooltipEl = mouseTimeTooltip.el(); + tooltipStyleOrig = tooltipEl.style; plugin.setState({ - ready: !!(mouseTimeTooltip && (options.urlArray.length || options.url) && + ready: !!((options.urlArray.length || options.url) && intCheck('width') && intCheck('height') && intCheck('columns') && intCheck('rows') && downlinkCheck()) }); diff --git a/test/sprite-thumbnails.test.js b/test/sprite-thumbnails.test.js index 7cba897..702a260 100644 --- a/test/sprite-thumbnails.test.js +++ b/test/sprite-thumbnails.test.js @@ -37,8 +37,17 @@ QUnit.module('videojs-sprite-thumbnails', { } }); -QUnit.test('changes ready state', function(assert) { - assert.expect(7); +QUnit.test('checking controls default tree', function(assert) { + assert.expect(2); + + const mouseTimeDisplay = this.player.controlBar.progressControl.seekBar.mouseTimeDisplay; + + mouseTimeDisplay.removeChild('TimeTooltip'); + assert.strictEqual( + mouseTimeDisplay.getChild('TimeTooltip'), + null, + 'removed mouse display time tooltip: no default controls tree' + ); this.player.spriteThumbnails({ url: '../img/oceans-thumbs.jpg', @@ -47,11 +56,37 @@ QUnit.test('changes ready state', function(assert) { columns: 10 }).log.level('all'); + this.clock.tick(1); + this.player.trigger('loadedmetadata'); + assert.strictEqual( this.player.spriteThumbnails().state.ready, false, - 'the plugin is not ready to show thumbnails' + 'no default controls tree: plugin not ready' ); +}); + +QUnit.test('changes ready state', function(assert) { + assert.expect(7); + + this.player.spriteThumbnails({ + url: '../img/oceans-thumbs.jpg', + width: 240, + height: 100 + }).log.level('all'); + + this.player.trigger('loadedmetadata'); + + assert.strictEqual( + this.player.spriteThumbnails().state.ready, + false, + 'no columns given, plugin not ready to show thumbnails' + ); + + this.player.src({src: 'dummy.mp4', spriteThumbnails: { + url: '../img/oceans-thumbs.jpg', + columns: 10 + }}); this.clock.tick(1); this.player.trigger('loadedmetadata'); @@ -59,7 +94,7 @@ QUnit.test('changes ready state', function(assert) { assert.strictEqual( this.player.spriteThumbnails().state.ready, true, - 'the plugin is now able to show thumbnails on ready' + 'the plugin is now able to show thumbnails' ); assert.strictEqual( this.player.hasClass('vjs-thumbnails-ready'), @@ -93,7 +128,6 @@ QUnit.test('changes ready state', function(assert) { 'options empty: disabled plugin inherits options, except for url' ); - this.clock.tick(1); this.player.src({src: 'dummy.mp4', spriteThumbnails: {url: '../img/oceans-thumbs.jpg'}}); this.player.trigger('loadedmetadata');