From 4cf82d87e1a9e3d84d1b9a5153086645203e121b Mon Sep 17 00:00:00 2001 From: Yonatan Kra Date: Thu, 30 Mar 2023 14:41:27 +0300 Subject: [PATCH] feat(vwc-audio): audio infinity time handling (VIV-874) (#1383) * feat(vwc-audio): visualize Infinity duration --- components/audio/src/vwc-audio.ts | 12 ++- components/audio/test/audio.test.js | 71 +++++++++++++++--- .../media-controller/src/vwc-scrub-bar.ts | 12 ++- ui-tests/snapshots/vwc-audio.png | Bin 1839 -> 2868 bytes ui-tests/tests/vwc-audio/index.js | 2 +- 5 files changed, 81 insertions(+), 16 deletions(-) diff --git a/components/audio/src/vwc-audio.ts b/components/audio/src/vwc-audio.ts index 4999dbfa62..2e054011ce 100644 --- a/components/audio/src/vwc-audio.ts +++ b/components/audio/src/vwc-audio.ts @@ -26,7 +26,6 @@ const SECOND = 1; const MINUTE = 60 * SECOND; const HOUR = 60 * MINUTE; - const setEvents = function (eventSource: HTMLElement, handlersMap: Record unknown>) { return (pipe as any)(...Object .entries(handlersMap) @@ -36,7 +35,6 @@ const setEvents = function (eventSource: HTMLElement, handlersMap: Record { const outputTime: Array = []; [HOUR, MINUTE, SECOND].reduce((ac: number, divider: number) => { @@ -49,6 +47,11 @@ const formatTime = (seconds: number) => { .join(':'); }; +function getTimeStampTemplate(_playheadPosition: number, _duration: number) { + const timeString = _duration === Infinity ? '__ / __' : `${formatTime(_playheadPosition)} / ${formatTime(_duration)}`; + return html`
${timeString}
`; +} + type AudioConnotation = Connotation.Primary | Connotation.CTA; @@ -138,6 +141,7 @@ export class VWCAudio extends LitElement { } override render(): TemplateResult { + return html`
@@ -152,8 +156,8 @@ export class VWCAudio extends LitElement { type="${this._isPlaying ? 'pause-solid' : 'play-solid'}" > - ${this.timestamp ? html`
${formatTime(this._playheadPosition)} / ${formatTime(this._duration)}
` : nothing} - ${!this.noseek ? html`` : nothing} + ${this.timestamp ? getTimeStampTemplate(this._playheadPosition, this._duration) : nothing} + ${!this.noseek ? html`` : nothing}
`; } diff --git a/components/audio/test/audio.test.js b/components/audio/test/audio.test.js index b8970fbbac..e88d02ecb4 100644 --- a/components/audio/test/audio.test.js +++ b/components/audio/test/audio.test.js @@ -32,18 +32,71 @@ describe('vwc-audio', () => { expect(actualElement.src).to.eq(url); }); - it(`should not show scrub-bar if noseek is set`, async function () { - const [vwcAudioEl] = addElements(textToDomToParent(``)); - await waitNextTask(); - expect(vwcAudioEl.shadowRoot.querySelector('vwc-scrub-bar')).not.to.exist; + describe('scrub-bar', function () { + it(`should not show scrub-bar if noseek is set`, async function () { + const [vwcAudioEl] = addElements(textToDomToParent(``)); + await waitNextTask(); + expect(vwcAudioEl.shadowRoot.querySelector('vwc-scrub-bar')).not.to.exist; + }); + + it('should add noseek-button to the scrub-bar when duration is Infinity', async function () { + const [vwcAudioEl] = addElements(textToDomToParent(``)); + await waitNextTask(); + Object.defineProperty(vwcAudioEl._audio, 'duration', { + get: () => Infinity + }); + vwcAudioEl._audio.dispatchEvent(new Event('loadedmetadata')); + await waitNextTask(); + expect(vwcAudioEl.shadowRoot.querySelector('vwc-scrub-bar').hasAttribute('noseek-button')) + .to.equal(true); + }); }); - it(`should show timestamp indicator when "timestamp" is set`, async function () { - const [vwcAudioEl] = addElements(textToDomToParent(``)); - await waitNextTask(); - expect(vwcAudioEl.shadowRoot.querySelector('.playhead-position')).to.exist; + describe('timestamp', function () { + it(`should show timestamp indicator when "timestamp" is set`, async function () { + const [vwcAudioEl] = addElements(textToDomToParent(``)); + await waitNextTask(); + expect(vwcAudioEl.shadowRoot.querySelector('.playhead-position')).to.exist; + }); + + it(`should hide timestamp indicator when "timestamp" is not set`, async function () { + const [vwcAudioEl] = addElements(textToDomToParent(``)); + await waitNextTask(); + expect(vwcAudioEl.shadowRoot.querySelector('.playhead-position')).not.to.exist; + }); + + it(`should show the end time correctly`, async function () { + const [vwcAudioEl] = addElements(textToDomToParent(``)); + vwcAudioEl._duration = 500; + await waitNextTask(); + expect(vwcAudioEl.shadowRoot.querySelector('.playhead-position').textContent).to.equal('0:00 / 8:20'); + }); + + it(`should show the current time correctly`, async function () { + const [vwcAudioEl] = addElements(textToDomToParent(``)); + await waitNextTask(); + vwcAudioEl._duration = 500; + Object.defineProperty(vwcAudioEl._audio, 'currentTime', { + get: () => 500 + }); + vwcAudioEl._audio.dispatchEvent(new Event('timeupdate')); + await waitNextTask(); + expect(vwcAudioEl.shadowRoot.querySelector('.playhead-position').textContent).to.equal('8:20 / 8:20'); + }); + + it('should show null timestamp when duration is infinity', async function () { + const [vwcAudioEl] = addElements(textToDomToParent(``)); + await waitNextTask(); + Object.defineProperty(vwcAudioEl._audio, 'duration', { + get: () => Infinity + }); + vwcAudioEl._audio.dispatchEvent(new Event('loadedmetadata')); + await waitNextTask(); + expect(vwcAudioEl.shadowRoot.querySelector('.playhead-position').textContent).to.equal('__ / __'); + }); }); + describe('play', () => { let originalPlay = Audio.prototype.play; before(function () { @@ -58,7 +111,7 @@ describe('vwc-audio', () => { it('should return to stop mode when src changes', async function () { - const [audioElement] = (textToDomToParent(``)); + const [audioElement] = addElements(textToDomToParent(``)); await waitNextTask(); audioElement.src = 'https://download.samplelib.com/mp3/sample-9s.mp3'; await audioElement.updateComplete; diff --git a/components/media-controller/src/vwc-scrub-bar.ts b/components/media-controller/src/vwc-scrub-bar.ts index 5a4e18bbc2..d926bd08f6 100644 --- a/components/media-controller/src/vwc-scrub-bar.ts +++ b/components/media-controller/src/vwc-scrub-bar.ts @@ -5,7 +5,7 @@ import kefir from 'kefir'; import { pipe, partial, clamp, prop, always, not, identity, path } from 'ramda'; -import { style as vwcScrubBarStyle } from './vwc-scrub-bar.css.js';// +import { style as vwcScrubBarStyle } from './vwc-scrub-bar.css.js'; const SIGNAL = Symbol('signal'), TRACK_KNOB_HORIZONTAL_MARGIN = 5, @@ -63,6 +63,13 @@ const byType = typeName => ({ type }) => type === typeName, * @fires userSkipBackwardsRequest - Fires when the user requests a skip backwards */ class VWCScrubBar extends HTMLElement { + static get observedAttributes() { return ['noseek-button']; } + + attributeChangedCallback(name, oldValue, newValue) { + if (name === 'noseek-button' && oldValue !== newValue) { + this.trackEl.querySelector('button')?.style.display = newValue !== null ? 'none' : 'block'; + } + } constructor() { super(); @@ -111,7 +118,6 @@ class VWCScrubBar extends HTMLElement { 'resize', ].map(eventName => kefir.fromEvents(window, eventName)), trackBarEnabledProperty = componentConnectedStream - .take(1) .map(() => { // eslint-disable-next-line return !this.hasAttribute('noseek'); @@ -307,6 +313,8 @@ class VWCScrubBar extends HTMLElement { [KEY_RIGHT]: 'userSkipForwardRequest' })[keyCode]); }); + + this.trackEl = trackEl; } /** diff --git a/ui-tests/snapshots/vwc-audio.png b/ui-tests/snapshots/vwc-audio.png index d969109b0949c11d4161c855fef1aad87344a104..429895c93d94f54bfc456b645c1c21ec00978680 100644 GIT binary patch literal 2868 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 GbN>ULnkm!( diff --git a/ui-tests/tests/vwc-audio/index.js b/ui-tests/tests/vwc-audio/index.js index a5e8e4fac1..e215fcfd09 100644 --- a/ui-tests/tests/vwc-audio/index.js +++ b/ui-tests/tests/vwc-audio/index.js @@ -7,7 +7,7 @@ export async function createElementVariations(wrapper) { testWrapper.classList.add('grid'); testWrapper.innerHTML = ` - + `; wrapper.appendChild(testWrapper);