Skip to content

Commit

Permalink
feat(vwc-audio): audio infinity time handling (VIV-874) (#1383)
Browse files Browse the repository at this point in the history
* feat(vwc-audio): visualize Infinity duration
  • Loading branch information
YonatanKra authored Mar 30, 2023
1 parent 50e1c22 commit 4cf82d8
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 16 deletions.
12 changes: 8 additions & 4 deletions components/audio/src/vwc-audio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ const SECOND = 1;
const MINUTE = 60 * SECOND;
const HOUR = 60 * MINUTE;


const setEvents = function (eventSource: HTMLElement, handlersMap: Record<string, () => unknown>) {
return (pipe as any)(...Object
.entries(handlersMap)
Expand All @@ -36,7 +35,6 @@ const setEvents = function (eventSource: HTMLElement, handlersMap: Record<string
}));
};

/* istanbul ignore next */
const formatTime = (seconds: number) => {
const outputTime: Array<number> = [];
[HOUR, MINUTE, SECOND].reduce((ac: number, divider: number) => {
Expand All @@ -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`<div class="playhead-position">${timeString}</div>`;
}

type AudioConnotation =
Connotation.Primary |
Connotation.CTA;
Expand Down Expand Up @@ -138,6 +141,7 @@ export class VWCAudio extends LitElement {
}

override render(): TemplateResult {

return html`
<audio class='audio-el' src='${ifDefined(this.src)}'></audio>
<div class="audio ${classMap(this.getRenderClasses())}" aria-controls="${ifDefined(this.ariaControls)}">
Expand All @@ -152,8 +156,8 @@ export class VWCAudio extends LitElement {
type="${this._isPlaying ? 'pause-solid' : 'play-solid'}"
></vwc-icon>
</button>
${this.timestamp ? html`<div class="playhead-position">${formatTime(this._playheadPosition)} / ${formatTime(this._duration)}</div>` : nothing}
${!this.noseek ? html`<vwc-scrub-bar @userScrubRequest="${({ detail }: { detail: number }) => this.currentTime = detail * this._duration}" class="scrubber"></vwc-scrub-bar>` : nothing}
${this.timestamp ? getTimeStampTemplate(this._playheadPosition, this._duration) : nothing}
${!this.noseek ? html`<vwc-scrub-bar noseek-button=${ifDefined(this._duration === Infinity ? 'true' : undefined)} @userScrubRequest="${({ detail }: { detail: number }) => this.currentTime = detail * this._duration}" class="scrubber"></vwc-scrub-bar>` : nothing}
</div>
`;
}
Expand Down
71 changes: 62 additions & 9 deletions components/audio/test/audio.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(`<vwc-audio noseek></vwc-audio>`));
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(`<vwc-audio noseek></vwc-audio>`));
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(`<vwc-audio></vwc-audio>`));
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(`<vwc-audio timestamp></vwc-audio>`));
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(`<vwc-audio timestamp></vwc-audio>`));
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(`<vwc-audio></vwc-audio>`));
await waitNextTask();
expect(vwcAudioEl.shadowRoot.querySelector('.playhead-position')).not.to.exist;
});

it(`should show the end time correctly`, async function () {
const [vwcAudioEl] = addElements(textToDomToParent(`<vwc-audio timestamp></vwc-audio>`));
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(`<vwc-audio timestamp></vwc-audio>`));
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(`<vwc-audio timestamp></vwc-audio>`));
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 () {
Expand All @@ -58,7 +111,7 @@ describe('vwc-audio', () => {

it('should return to stop mode when src changes', async function () {

const [audioElement] = (textToDomToParent(`<vwc-audio></vwc-audio>`));
const [audioElement] = addElements(textToDomToParent(`<vwc-audio></vwc-audio>`));
await waitNextTask();
audioElement.src = 'https://download.samplelib.com/mp3/sample-9s.mp3';
await audioElement.updateComplete;
Expand Down
12 changes: 10 additions & 2 deletions components/media-controller/src/vwc-scrub-bar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -307,6 +313,8 @@ class VWCScrubBar extends HTMLElement {
[KEY_RIGHT]: 'userSkipForwardRequest'
})[keyCode]);
});

this.trackEl = trackEl;
}

/**
Expand Down
Binary file modified ui-tests/snapshots/vwc-audio.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion ui-tests/tests/vwc-audio/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export async function createElementVariations(wrapper) {
testWrapper.classList.add('grid');
testWrapper.innerHTML =
`
<vwc-audio></vwc-audio>
<vwc-audio timestamp></vwc-audio>
<vwc-audio connotation="primary"></vwc-audio>
<vwc-audio noseek="true"></vwc-audio>`;
wrapper.appendChild(testWrapper);
Expand Down

0 comments on commit 4cf82d8

Please sign in to comment.