@outbook/webcomponents-player 1.5.3 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @outbook/webcomponents-player
2
2
 
3
- This package provides a web component for an audio player with playlist functionality.
3
+ This package provides a web component for an audio player with playlist functionality. It includes playback controls, a timeline, track information with artwork, prominent-color backgrounds, and OS media controls through the [Media Session API](https://developer.mozilla.org/en-US/docs/Web/API/Media_Session_API) when supported.
4
4
 
5
5
  ## Installation
6
6
 
@@ -22,20 +22,14 @@ const myPlaylist = [
22
22
  title: 'Song Title 1',
23
23
  artist: 'Artist Name 1',
24
24
  album: 'Album Name 1',
25
- cover: {
26
- name: 'cover1.jpg' // or a URL
27
- // Other cover properties if available
28
- }
25
+ artwork: '/artwork/[size]/[density]/cover1.jpg'
29
26
  },
30
27
  {
31
28
  file: 'path/to/your/audio2.mp3',
32
29
  title: 'Song Title 2',
33
30
  artist: 'Artist Name 2',
34
31
  album: 'Album Name 2',
35
- cover: {
36
- name: 'cover2.png' // or a URL
37
- // Other cover properties if available
38
- }
32
+ artwork: '/artwork/[size]/[density]/cover2.jpg'
39
33
  }
40
34
  ];
41
35
 
@@ -74,18 +68,14 @@ import '@outbook/webcomponents-player';
74
68
  "title": "Song Title 1",
75
69
  "artist": "Artist Name 1",
76
70
  "album": "Album Name 1",
77
- "cover": {
78
- "name": "cover1.jpg"
79
- }
71
+ "artwork": "/artwork/[size]/[density]/cover1.jpg"
80
72
  },
81
73
  {
82
74
  "file": "path/to/your/audio2.mp3",
83
75
  "title": "Song Title 2",
84
76
  "artist": "Artist Name 2",
85
77
  "album": "Album Name 2",
86
- "cover": {
87
- "name": "cover2.png"
88
- }
78
+ "artwork": "/artwork/[size]/[density]/cover2.jpg"
89
79
  }
90
80
  ]'
91
81
  ></outbook-player>
@@ -93,21 +83,36 @@ import '@outbook/webcomponents-player';
93
83
 
94
84
  **Note:** When using the `playlist` property directly in HTML, you must pass a JSON string. Ensure the JSON is valid and properly escaped if necessary.
95
85
 
86
+ ### Track Object
87
+
88
+ Each item in `playlist` describes one track:
89
+
90
+ | Field | Type | Required | Description |
91
+ | --------- | -------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
92
+ | `file` | `String` | Yes | URL of the audio file. Used as the `src` of the underlying `<audio>` element. |
93
+ | `title` | `String` | No | Track title, shown in the playlist, track info, and OS media controls. |
94
+ | `artist` | `String` | No | Artist name. |
95
+ | `album` | `String` | No | Album name. |
96
+ | `artwork` | `String` | No | Artwork URL template. `[size]` and `[density]` placeholders are replaced with the requested size (e.g. `384`) and density (e.g. `1x`). Example: `/artwork/[size]/[density]/cover.jpg`. |
97
+ | `cover` | `Object` | No | Optional. Only `cover.extension` (e.g. `'jpg'`, `'png'`) is read, to set the artwork MIME type for OS media controls. |
98
+
99
+ Tracks without `artwork` are shown without cover art, and the background falls back to the default color.
100
+
96
101
  ## Component: `outbook-player`
97
102
 
98
103
  This is the underlying web component. It can be used directly in HTML or in any framework.
99
104
 
100
105
  ### Properties
101
106
 
102
- | Attribute | Property | Type | Default | Description |
103
- | --------------------- | ------------------- | --------- | ----------- | ------------------------------------------------------------------------------------------------ |
104
- | `lang` | `lang` | `String` | `'en'` | The language to use for localized strings. |
105
- | `playlist` | `playlist` | `Array` | `[]` | An array of track objects, each with `file`, `title`, `artist`, `album`, and `cover` properties. |
106
- | `extraClasses` | `extraClasses` | `String` | `undefined` | Classes to add to the host element. |
107
- | `show-file-extension` | `showFileExtension` | `Boolean` | `true` | Whether to display file extensions in the playlist. |
108
- | `show-artwork` | `showArtwork` | `Boolean` | `true` | Whether to display artwork in the playlist. |
109
- | `show-track-number` | `showTrackNumber` | `Boolean` | `true` | Whether to display track numbers in the playlist. |
110
- | `playlist-align` | `playlistAlign` | `String` | `'right'` | Aligns the playlist to 'left' or 'right' of the track info. |
107
+ | Attribute | Property | Type | Default | Description |
108
+ | --------------------- | ------------------- | --------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------- |
109
+ | `lang` | `lang` | `String` | `'en'` | The language to use for localized strings. |
110
+ | `playlist` | `playlist` | `Array` | `[]` | An array of track objects, each with `file`, `title`, `artist`, `album`, and `artwork` properties (see [Track Object](#track-object)). |
111
+ | `extraClasses` | `extraClasses` | `String` | `undefined` | Classes to add to the host element. |
112
+ | `show-file-extension` | `showFileExtension` | `Boolean` | `true` | Whether to display file extensions in the playlist. |
113
+ | `show-artwork` | `showArtwork` | `Boolean` | `true` | Whether to display artwork in the playlist. |
114
+ | `show-track-number` | `showTrackNumber` | `Boolean` | `true` | Whether to display track numbers in the playlist. |
115
+ | `playlist-align` | `playlistAlign` | `String` | `'right'` | Aligns the playlist to 'left' or 'right' of the track info. |
111
116
 
112
117
  ### Styling
113
118
 
@@ -0,0 +1,40 @@
1
+ // get-background-color.js
2
+
3
+ import { prominent } from 'color.js';
4
+ import { rgbToHex } from './rgb-to-hex.js';
5
+ import { orderByLuminance } from './order-by-luminance.js';
6
+
7
+ export const emptyBackground = '';
8
+
9
+ const PROMINENT_OPTIONS = {
10
+ amount: 5,
11
+ format: 'rgb',
12
+ group: 40,
13
+ sample: 10
14
+ };
15
+
16
+ /**
17
+ * Extracts prominent colors from a cover image and returns a CSS block
18
+ * of custom properties ordered by luminance.
19
+ *
20
+ * @param {string} [coverUrl]
21
+ * @returns {Promise<string>} CSS string like `--player--main-color-0: #aabbcc;...`
22
+ */
23
+ export async function getBackgroundColor(coverUrl) {
24
+ if (!coverUrl) return emptyBackground;
25
+
26
+ try {
27
+ const colors = await prominent(coverUrl, PROMINENT_OPTIONS);
28
+
29
+ const colorPropsRgb = colors.map(([r, g, b]) => ({ r, g, b }));
30
+ const orderedColorsRgb = orderByLuminance(colorPropsRgb);
31
+
32
+ return orderedColorsRgb
33
+ .map((item, index) => `--player--main-color-${index}: ${rgbToHex(item)};`)
34
+ .join('\n');
35
+ } catch (err) {
36
+ // color.js throws on unreadable images / CORS / network failures
37
+ console.warn('getBackgroundColor failed:', err);
38
+ return emptyBackground;
39
+ }
40
+ }
@@ -1,9 +1,33 @@
1
+ // order-by-luminance.js
1
2
  // https://en.wikipedia.org/wiki/Luma_(video)
2
- const calculateLuminance = p => {
3
- return 0.2126 * p.r + 0.7152 * p.g + 0.0722 * p.b;
4
- };
3
+
4
+ /**
5
+ * Calculate relative luminance for an RGB object using Rec. 709 coefficients.
6
+ * @param {{r: number, g: number, b: number}} p
7
+ * @returns {number}
8
+ */
9
+ export const calculateLuminance = ({ r, g, b }) =>
10
+ 0.2126 * r + 0.7152 * g + 0.0722 * b;
11
+
12
+ /**
13
+ * Sort an array of RGB objects by luminance, brightest first.
14
+ * Does not mutate the input array.
15
+ *
16
+ * @param {Array<{r: number, g: number, b: number}>} rgbValues
17
+ * @returns {Array<{r: number, g: number, b: number}>} new sorted array
18
+ */
5
19
  export const orderByLuminance = rgbValues => {
6
- return rgbValues.sort((p1, p2) => {
7
- return calculateLuminance(p2) - calculateLuminance(p1);
8
- });
20
+ if (!Array.isArray(rgbValues)) {
21
+ throw new TypeError('orderByLuminance expects an array');
22
+ }
23
+
24
+ return rgbValues
25
+ .map((rgb, index) => ({ rgb, index, luminance: calculateLuminance(rgb) }))
26
+ .sort((a, b) => {
27
+ // Descending luminance
28
+ if (b.luminance !== a.luminance) return b.luminance - a.luminance;
29
+ // Stable tie-break on original position
30
+ return a.index - b.index;
31
+ })
32
+ .map(({ rgb }) => rgb);
9
33
  };
@@ -1,13 +1,8 @@
1
1
  import { virtual } from 'haunted';
2
- import { html, nothing } from 'lit';
3
- import { isKeyEnterOrKeySpace, isEventClick } from '@outbook/a11y';
2
+ import { html } from 'lit';
4
3
  import { classMap } from 'lit/directives/class-map.js';
5
- import { TrackText } from '../track-text/index.js';
6
- import { TypeIcon } from '@outbook/webcomponents-type-icon/shadow';
7
- import { play_arrow, pause } from '@outbook/icons';
8
- import { BadgeFileExtension } from '@outbook/webcomponents-badge-file-extension';
9
- import { ItemDuration } from './modules/item-duration.js';
10
- import { ShowCover } from './modules/show-cover.js';
4
+
5
+ import { PlayerPlaylistItem } from '@outbook/webcomponents-player-playlist-item';
11
6
 
12
7
  export const Playlist = virtual(
13
8
  ({
@@ -20,11 +15,9 @@ export const Playlist = virtual(
20
15
  }) => {
21
16
  const { showArtwork, showTrackNumber, showFileExtension } = list;
22
17
  function handleButton(index) {
23
- return (ev) => {
24
- if (isEventClick(ev) || isKeyEnterOrKeySpace(ev)) {
25
- handleAudio.setTrack({ index, fromControls: false });
26
- }
27
- }
18
+ return () => {
19
+ handleAudio.setTrack({ index, fromControls: false });
20
+ };
28
21
  }
29
22
  return html`
30
23
  <div class="player-playlist" id="${listId}">
@@ -37,59 +30,27 @@ export const Playlist = virtual(
37
30
  });
38
31
  return html`
39
32
  <li class="${itemClasses}" data-playlist-item="${index}">
40
- <div
41
- class="player-playlist__item-button"
42
- tabindex="0"
43
- role="button"
44
- @click="${handleButton(index)}"
45
- @keydown="${handleButton(index)}"
46
- >
47
- ${showArtwork ? ShowCover({ cover: item.cover }) : nothing}
48
- ${isPlayingItem
49
- ? html`
50
- <div
51
- class="player-playlist__item-block player-playlist__item-playing-icon"
52
- >
53
- ${TypeIcon({
54
- icon: isPlaying ? 'play_arrow' : 'pause',
55
- icons: { play_arrow, pause },
56
- extraClasses:
57
- 'player-playlist__item-playing-icon-inner'
58
- })}
59
- </div>
60
- `
61
- : nothing}
62
- ${showTrackNumber
63
- ? html`
64
- <div
65
- class="player-playlist__item-block track-text__item size-small"
66
- >
67
- ${item.trackNumber || index + 1}
68
- </div>
69
- `
70
- : nothing}
71
- <div class="player-playlist__item-block">
72
- ${TrackText({
73
- title: item.title,
74
- artist: item.artist,
75
- album: item.album
76
- })}
77
- </div>
78
- ${item.duration || showFileExtension
79
- ? html`
80
- <div class="player-playlist__item-group is-last-right">
81
- ${ItemDuration({ duration: item.duration })}
82
- ${showFileExtension
83
- ? html`
84
- <div class="player-playlist__item-block">
85
- ${BadgeFileExtension({ text: item.format })}
86
- </div>
87
- `
88
- : nothing}
89
- </div>
90
- `
91
- : nothing}
92
- </div>
33
+ ${PlayerPlaylistItem({
34
+ ...item,
35
+ title: item.title,
36
+ artist: item.artist,
37
+ album: item.album,
38
+ duration: item.duration,
39
+ format: item.format,
40
+ artwork: item.artwork,
41
+ trackNumber: item.trackNumber,
42
+ trackIndex: index,
43
+ isCurrent: isPlayingItem,
44
+ playState: isPlayingItem
45
+ ? isPlaying
46
+ ? 'playing'
47
+ : 'paused'
48
+ : 'stopped',
49
+ showArtwork,
50
+ showTrackNumber,
51
+ showFileExtension,
52
+ eventFn: handleButton(index)
53
+ })}
93
54
  </li>
94
55
  `;
95
56
  })}
@@ -1,13 +1,10 @@
1
- const componentToHex = channel => {
2
- const hex = channel.toString(16);
3
- return hex.length === 1 ? '0' + hex : hex;
1
+ // rgb-to-hex.js
2
+
3
+ export const componentToHex = channel => {
4
+ const clamped = Math.max(0, Math.min(255, Math.round(channel)));
5
+ return clamped.toString(16).padStart(2, '0');
4
6
  };
5
7
 
6
- export const rgbToHex = item => {
7
- return (
8
- '#' +
9
- componentToHex(item.r) +
10
- componentToHex(item.g) +
11
- componentToHex(item.b)
12
- ).toUpperCase();
8
+ export const rgbToHex = ({ r, g, b }) => {
9
+ return `#${componentToHex(r)}${componentToHex(g)}${componentToHex(b)}`.toUpperCase();
13
10
  };
@@ -1,4 +1,4 @@
1
- import { getCover } from './get-cover.js';
1
+ import { playerGetArtwork } from '@outbook/player-get-artwork';
2
2
  import { isValidTrackIndex } from './is-valid-track-index.js';
3
3
 
4
4
  const MEDIA_SESSION_ACTIONS = [
@@ -44,7 +44,11 @@ export function setupMediaSession({ owner, index, playlist, handleAudio }) {
44
44
 
45
45
  mediaSessionOwner = owner;
46
46
  const metadata = playlist[index];
47
- const coverUrl = getCover(metadata.cover);
47
+ const coverUrl = playerGetArtwork({
48
+ artwork: metadata.artwork,
49
+ size: 512,
50
+ density: '1x'
51
+ });
48
52
  const extension = metadata.cover?.extension?.toLowerCase().replace(/^\./, '');
49
53
  const subtype = { jpg: 'jpeg', svg: 'svg+xml' }[extension] || extension;
50
54
  const type = subtype ? `image/${subtype}` : undefined;
@@ -1,5 +1,11 @@
1
+ // to-boolean.js
2
+
1
3
  export function toBoolean(value) {
2
- if (typeof value === 'boolean') return value;
3
- if (typeof value === 'string') return value !== 'false';
4
+ if (typeof value === 'boolean') {
5
+ return value;
6
+ }
7
+ if (typeof value === 'string') {
8
+ return value !== 'false';
9
+ }
4
10
  return Boolean(value);
5
11
  }
@@ -1,27 +1,38 @@
1
1
  import { html } from 'lit';
2
2
  import { TrackText } from '../track-text/index.js';
3
- import { getCover } from '../get-cover.js';
4
3
  import { PlayerArtwork } from '@outbook/webcomponents-player-artwork';
5
4
 
6
5
  export function TrackInfo({ track = {}, indexSelected, literals = {} }) {
7
6
  const hasTrackInfo = Number.isInteger(indexSelected);
8
- const cover = hasTrackInfo && track.cover ? getCover(track.cover) : undefined;
7
+ const artwork = hasTrackInfo && track.artwork ? track.artwork : undefined;
9
8
  return html`
10
9
  <div class="track-info">
11
10
  <div class="track-info__inner">
12
11
  ${PlayerArtwork({
13
- src1x: cover,
12
+ src: artwork,
13
+ sources: [
14
+ {
15
+ media: '(min-width: 768px)',
16
+ sizes: [384]
17
+ },
18
+ {
19
+ media: '(min-width: 0px)',
20
+ sizes: [96]
21
+ }
22
+ ],
14
23
  extraClasses: 'track-info__image'
15
24
  })}
16
25
  <div class="track-info__data">
17
- ${hasTrackInfo
18
- ? TrackText({
19
- title: track.title,
20
- artist: track.artist,
21
- album: track.album,
22
- hasTrackInfo
23
- })
24
- : TrackText({ title: literals.noPlaying, hasTrackInfo })}
26
+ ${
27
+ hasTrackInfo
28
+ ? TrackText({
29
+ title: track.title,
30
+ artist: track.artist,
31
+ album: track.album,
32
+ hasTrackInfo
33
+ })
34
+ : TrackText({ title: literals.noPlaying, hasTrackInfo })
35
+ }
25
36
  </div>
26
37
  </div>
27
38
  </div>
@@ -283,43 +283,6 @@ button[disabled],
283
283
  height: var(--_controls-size);
284
284
  }
285
285
 
286
- .player-playlist {
287
- --_playlist-font-weight: normal;
288
- --_playlist-font-size: 0.75rem;
289
- --_playlist-font-size-small: 0.625rem;
290
- --_playlist-item-background-color: transparent;
291
- --_playlist-item-image-size: 3rem;
292
- }
293
- .player-playlist .is-playing {
294
- --_playlist-font-weight: 600;
295
- --_playlist-item-background-color: var(--_accent-200);
296
- }
297
- .ambient-dark .player-playlist .is-playing {
298
- --_playlist-item-background-color: var(--_accent-700);
299
- }
300
-
301
- @container player (width >= 768px) {
302
- .player-playlist {
303
- --_playlist-font-size: 0.875rem;
304
- --_playlist-font-size-small: 0.75rem;
305
- }
306
- }
307
- @container player (width >= 1024px) {
308
- .player-playlist {
309
- --_playlist-font-size: 1rem;
310
- --_playlist-font-size-small: 0.875rem;
311
- --_playlist-item-image-size: 4rem;
312
- }
313
- }
314
- .player-playlist .track-text__item {
315
- font-weight: var(--_playlist-font-weight);
316
- font-size: var(--_playlist-font-size);
317
- line-height: 150%;
318
- }
319
- .player-playlist .track-text__item.size-small {
320
- font-size: var(--_playlist-font-size-small);
321
- }
322
-
323
286
  .player-playlist__items {
324
287
  padding: 0;
325
288
  position: relative;
@@ -355,59 +318,6 @@ button[disabled],
355
318
  border-bottom: 0;
356
319
  }
357
320
 
358
- .player-playlist__item-button {
359
- display: flex;
360
- width: 100%;
361
- border: 0;
362
- padding: 0.5rem;
363
- position: relative;
364
- align-items: center;
365
- color: inherit;
366
- background: transparent;
367
- font: inherit;
368
- text-align: inherit;
369
- cursor: pointer;
370
- }
371
- .player-playlist__item-button:focus-visible {
372
- outline: var(--outbook-outline--color, light-dark(oklch(48.8% 0.243 264.376deg), oklch(88.2% 0.059 254.128deg))) solid 2px;
373
- outline-offset: -4px;
374
- }
375
-
376
- .player-playlist__item-group,
377
- .player-playlist__item-block {
378
- display: flex;
379
- align-items: center;
380
- margin-left: 1rem;
381
- height: var(--_playlist-item-image-size);
382
- }
383
- .player-playlist__item-group:first-child,
384
- .player-playlist__item-block:first-child {
385
- margin-left: 0;
386
- }
387
- .player-playlist__item-group.is-last-right,
388
- .player-playlist__item-block.is-last-right {
389
- margin-left: auto;
390
- padding-left: 0.5rem;
391
- }
392
-
393
- .player-playlist__item-image {
394
- width: var(--_playlist-item-image-size);
395
- height: var(--_playlist-item-image-size);
396
- flex-shrink: 0;
397
- }
398
-
399
- .player-playlist__item-playing-icon {
400
- width: 1rem;
401
- height: 1rem;
402
- display: flex;
403
- align-items: center;
404
- }
405
-
406
- .player-playlist__item-playing-icon-inner {
407
- width: 100%;
408
- aspect-ratio: 1/1;
409
- }
410
-
411
321
  .track-info {
412
322
  --track-info--flex-direction: row;
413
323
  --track-info--image-width: 4rem;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@outbook/webcomponents-player",
3
- "version": "1.5.3",
3
+ "version": "1.6.1",
4
4
  "main": "player.js",
5
5
  "type": "module",
6
6
  "private": false,
@@ -35,8 +35,10 @@
35
35
  "@outbook/webcomponents-scrollable": ">=1.1.0",
36
36
  "@outbook/webcomponents-type-icon": ">=1.1.5",
37
37
  "@outbook/webcomponents-badge-file-extension": ">=1.3.0",
38
- "@outbook/webcomponents-player-artwork": ">=1.1.3",
38
+ "@outbook/webcomponents-player-artwork": ">=2.0.1",
39
39
  "@outbook/webcomponents-player-timeline": ">=1.0.8",
40
+ "@outbook/webcomponents-player-playlist-item": ">=1.0.1",
41
+ "@outbook/player-get-artwork": ">=1.0.0",
40
42
  "@outbook/icons": ">=1.3.2",
41
43
  "@outbook/webcomponents-player-audio-decoy": ">=1.0.0"
42
44
  },
package/player.js CHANGED
@@ -9,8 +9,10 @@ import {
9
9
  syncMediaSession
10
10
  } from './_lib/setup-media-session.js';
11
11
  import { isValidTrackIndex } from './_lib/is-valid-track-index.js';
12
- import { emptyBackground, getBackgroundColor } from './_lib/background.js';
13
- import { getCover } from './_lib/get-cover.js';
12
+ import {
13
+ emptyBackground,
14
+ getBackgroundColor
15
+ } from './_lib/get-background-color.js';
14
16
  import { classMap } from 'lit/directives/class-map.js';
15
17
  import { Scrollable } from '@outbook/webcomponents-scrollable';
16
18
  import { Playlist as ComponentPlaylist } from './_lib/playlist/index.js';
@@ -218,9 +220,7 @@ function ComponentPlayer(element) {
218
220
  }
219
221
 
220
222
  selectedTrackRef.current = selectedTrack;
221
- setCoverUrl(
222
- selectedTrack.cover?.name ? getCover(selectedTrack.cover) : null
223
- );
223
+ setCoverUrl(selectedTrack.artwork || null);
224
224
  }, [indexSelected, playlist, playlistMediaSessionSignature]);
225
225
 
226
226
  useEffect(() => {
@@ -369,9 +369,7 @@ function ComponentPlayer(element) {
369
369
  handleScroll({ index });
370
370
  }
371
371
 
372
- setCoverUrl(
373
- playlist[index].cover?.name ? getCover(playlist[index].cover) : null
374
- );
372
+ setCoverUrl(playlist[index].artwork || null);
375
373
  requestInitialPlayback();
376
374
  return true;
377
375
  },
@@ -468,21 +466,23 @@ function ComponentPlayer(element) {
468
466
  </div>
469
467
  </div>
470
468
  ${isPlaying ? html`${PlayerAudioDecoy({ mode: 'auto' })}` : nothing}
471
- ${Number.isInteger(indexSelected)
472
- ? html`
473
- <audio
474
- src="${playlist[indexSelected]?.file}"
475
- id="${mediaId}"
476
- @ended="${handleAudioEnd}"
477
- @timeupdate="${handleTimeUpdate}"
478
- @loadedmetadata="${handleDurationChange}"
479
- @durationchange="${handleDurationChange}"
480
- @play="${handleAudioNativePlay}"
481
- @pause="${handleAudioNativePause}"
482
- @error="${handleAudioNativeError}"
483
- ></audio>
484
- `
485
- : nothing}
469
+ ${
470
+ Number.isInteger(indexSelected)
471
+ ? html`
472
+ <audio
473
+ src="${playlist[indexSelected]?.file}"
474
+ id="${mediaId}"
475
+ @ended="${handleAudioEnd}"
476
+ @timeupdate="${handleTimeUpdate}"
477
+ @loadedmetadata="${handleDurationChange}"
478
+ @durationchange="${handleDurationChange}"
479
+ @play="${handleAudioNativePlay}"
480
+ @pause="${handleAudioNativePause}"
481
+ @error="${handleAudioNativeError}"
482
+ ></audio>
483
+ `
484
+ : nothing
485
+ }
486
486
  `
487
487
  : html`Loading...`;
488
488
  }
@@ -1,28 +0,0 @@
1
- import { prominent } from 'color.js';
2
- import { rgbToHex } from './rgb-to-hex.js';
3
- import { orderByLuminance } from './order-by-luminance.js';
4
-
5
- export const emptyBackground = '';
6
-
7
- export function getBackgroundColor(coverUrl) {
8
- return prominent(coverUrl, {
9
- amount: 5,
10
- format: 'rgb',
11
- group: 40,
12
- sample: 10
13
- }).then(colors => {
14
- const colorPropsRgb = colors.map(item => ({
15
- r: item[0],
16
- g: item[1],
17
- b: item[2]
18
- }));
19
-
20
- const orderedColorsRgb = orderByLuminance(colorPropsRgb);
21
-
22
- return orderedColorsRgb
23
- .map((item, index) => {
24
- return `--player--main-color-${index}: ${rgbToHex(item)};`;
25
- })
26
- .join(''); // Return the string!
27
- });
28
- }
package/_lib/get-cover.js DELETED
@@ -1,22 +0,0 @@
1
- export function getCover(data) {
2
- if (!data || typeof data !== 'object') {
3
- return undefined;
4
- }
5
-
6
- const { path = '', extension, name } = data;
7
- if (typeof name !== 'string' || !name.trim()) {
8
- return undefined;
9
- }
10
-
11
- const normalizedName = name.trim();
12
- const fileName =
13
- typeof extension === 'string' && extension.trim()
14
- ? `${normalizedName}.${extension.trim().replace(/^\./, '')}`
15
- : normalizedName;
16
- const normalizedPath =
17
- typeof path === 'string' && path !== '/' ? path.replace(/\/+$/, '') : path;
18
-
19
- return normalizedPath
20
- ? `${normalizedPath}${normalizedPath === '/' ? '' : '/'}${fileName}`
21
- : fileName;
22
- }
@@ -1,16 +0,0 @@
1
- import { html } from 'lit';
2
- import {
3
- readable as formatTime,
4
- iso as formatTimeDuration
5
- } from '@outbook/format-time';
6
-
7
- export function ItemDuration({ duration }) {
8
- return html`
9
- <time
10
- class="player-playlist__item-block track-text__item size-small"
11
- datetime="${formatTimeDuration(duration)}"
12
- >
13
- ${formatTime(duration)}
14
- </time>
15
- `;
16
- }
@@ -1,12 +0,0 @@
1
- import { html } from 'lit';
2
- import { getCover } from '../../get-cover.js';
3
- import { PlayerArtwork } from '@outbook/webcomponents-player-artwork';
4
-
5
- export const ShowCover = ({ cover }) => {
6
- return html`
7
- ${PlayerArtwork({
8
- src1x: cover ? getCover(cover) : undefined,
9
- extraClasses: 'player-playlist__item-block player-playlist__item-image'
10
- })}
11
- `;
12
- };