@scarlett-player/embed 1.5.1 → 1.6.0

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.
@@ -4220,6 +4220,272 @@ function createAudioUIPlugin(config) {
4220
4220
  };
4221
4221
  return plugin;
4222
4222
  }
4223
+ var PLAYLIST_ICONS = {
4224
+ previous: '<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" focusable="false"><path d="M6 6h2v12H6V6zm3.5 6L18 6v12l-8.5-6z"/></svg>',
4225
+ next: '<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" focusable="false"><path d="M16 6h2v12h-2V6zM6 6l8.5 6L6 18V6z"/></svg>',
4226
+ list: '<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" focusable="false"><path d="M3 6h11v2H3V6zm0 5h11v2H3v-2zm0 5h7v2H3v-2zm13-1.5V8l6 3.5-6 3.5z"/></svg>'
4227
+ };
4228
+ var PlaylistSkipButton = class {
4229
+ constructor(plugin, direction) {
4230
+ this.plugin = plugin;
4231
+ this.direction = direction;
4232
+ this.clickHandler = () => {
4233
+ if (this.direction === "next") {
4234
+ this.plugin.next();
4235
+ } else {
4236
+ this.plugin.previous();
4237
+ }
4238
+ };
4239
+ const label = direction === "next" ? "Next item" : "Previous item";
4240
+ this.el = document.createElement("button");
4241
+ this.el.type = "button";
4242
+ this.el.className = `sp-control sp-playlist-skip sp-playlist-skip--${direction}`;
4243
+ this.el.setAttribute("aria-label", label);
4244
+ this.el.setAttribute("title", label);
4245
+ this.el.innerHTML = PLAYLIST_ICONS[direction];
4246
+ this.el.addEventListener("click", this.clickHandler);
4247
+ }
4248
+ render() {
4249
+ return this.el;
4250
+ }
4251
+ update() {
4252
+ const state = this.plugin.getState();
4253
+ if (state.tracks.length <= 1) {
4254
+ this.el.style.display = "none";
4255
+ return;
4256
+ }
4257
+ this.el.style.display = "";
4258
+ const enabled = this.direction === "next" ? state.hasNext : state.hasPrevious;
4259
+ this.el.disabled = !enabled;
4260
+ }
4261
+ destroy() {
4262
+ this.el.removeEventListener("click", this.clickHandler);
4263
+ this.el.remove();
4264
+ }
4265
+ };
4266
+ var PlaylistPanel = class {
4267
+ constructor(plugin, options) {
4268
+ this.plugin = plugin;
4269
+ this.options = options;
4270
+ this.open = false;
4271
+ this.renderedIds = "";
4272
+ this.toggleHandler = (event) => {
4273
+ event.stopPropagation();
4274
+ this.setOpen(!this.open);
4275
+ };
4276
+ this.documentClickHandler = () => {
4277
+ if (this.open) this.setOpen(false);
4278
+ };
4279
+ this.keydownHandler = (event) => {
4280
+ if (event.key === "Escape" && this.open) {
4281
+ this.setOpen(false);
4282
+ this.button.focus();
4283
+ }
4284
+ };
4285
+ this.el = document.createElement("div");
4286
+ this.el.className = "sp-playlist";
4287
+ this.button = document.createElement("button");
4288
+ this.button.type = "button";
4289
+ this.button.className = "sp-control sp-playlist__button";
4290
+ this.button.setAttribute("aria-label", "Playlist");
4291
+ this.button.setAttribute("title", "Playlist");
4292
+ this.button.setAttribute("aria-haspopup", "true");
4293
+ this.button.setAttribute("aria-expanded", "false");
4294
+ this.button.innerHTML = PLAYLIST_ICONS.list;
4295
+ this.panel = document.createElement("div");
4296
+ this.panel.className = "sp-playlist__panel";
4297
+ this.panel.setAttribute("role", "menu");
4298
+ this.panel.hidden = true;
4299
+ this.el.appendChild(this.button);
4300
+ this.el.appendChild(this.panel);
4301
+ this.button.addEventListener("click", this.toggleHandler);
4302
+ document.addEventListener("click", this.documentClickHandler);
4303
+ document.addEventListener("keydown", this.keydownHandler);
4304
+ }
4305
+ render() {
4306
+ return this.el;
4307
+ }
4308
+ update() {
4309
+ const state = this.plugin.getState();
4310
+ if (state.tracks.length <= 1) {
4311
+ this.el.style.display = "none";
4312
+ return;
4313
+ }
4314
+ this.el.style.display = "";
4315
+ const signature = state.tracks.map((track) => track.id).join("|");
4316
+ if (signature !== this.renderedIds) {
4317
+ this.renderedIds = signature;
4318
+ this.renderPanel(state.tracks, state.currentIndex);
4319
+ return;
4320
+ }
4321
+ this.markActive(state.currentIndex);
4322
+ }
4323
+ destroy() {
4324
+ this.button.removeEventListener("click", this.toggleHandler);
4325
+ document.removeEventListener("click", this.documentClickHandler);
4326
+ document.removeEventListener("keydown", this.keydownHandler);
4327
+ this.el.remove();
4328
+ }
4329
+ setOpen(open) {
4330
+ this.open = open;
4331
+ this.panel.hidden = !open;
4332
+ this.button.setAttribute("aria-expanded", String(open));
4333
+ this.el.classList.toggle("sp-playlist--open", open);
4334
+ }
4335
+ markActive(currentIndex) {
4336
+ const items = this.panel.querySelectorAll(".sp-playlist__item");
4337
+ items.forEach((item, index) => {
4338
+ item.classList.toggle("sp-playlist__item--active", index === currentIndex);
4339
+ item.setAttribute("aria-current", index === currentIndex ? "true" : "false");
4340
+ });
4341
+ }
4342
+ renderPanel(tracks, currentIndex) {
4343
+ this.panel.textContent = "";
4344
+ tracks.forEach((track, index) => {
4345
+ const item = document.createElement("button");
4346
+ item.type = "button";
4347
+ item.className = "sp-playlist__item";
4348
+ item.setAttribute("role", "menuitem");
4349
+ item.setAttribute("aria-current", index === currentIndex ? "true" : "false");
4350
+ if (index === currentIndex) {
4351
+ item.classList.add("sp-playlist__item--active");
4352
+ }
4353
+ const position = document.createElement("span");
4354
+ position.className = "sp-playlist__position";
4355
+ position.textContent = String(index + 1);
4356
+ const text = document.createElement("span");
4357
+ text.className = "sp-playlist__text";
4358
+ const title = document.createElement("span");
4359
+ title.className = "sp-playlist__title";
4360
+ title.textContent = track.title ?? `Item ${index + 1}`;
4361
+ text.appendChild(title);
4362
+ if (track.artist) {
4363
+ const artist = document.createElement("span");
4364
+ artist.className = "sp-playlist__artist";
4365
+ artist.textContent = track.artist;
4366
+ text.appendChild(artist);
4367
+ }
4368
+ item.appendChild(position);
4369
+ item.appendChild(text);
4370
+ item.addEventListener("click", (event) => {
4371
+ event.stopPropagation();
4372
+ this.options.onSelect(index);
4373
+ this.setOpen(false);
4374
+ });
4375
+ this.panel.appendChild(item);
4376
+ });
4377
+ }
4378
+ };
4379
+ var STYLE_ID = "sp-playlist-styles";
4380
+ var styles = `
4381
+ .sp-playlist-skip[disabled] {
4382
+ opacity: 0.4;
4383
+ cursor: default;
4384
+ }
4385
+
4386
+ .sp-playlist {
4387
+ position: relative;
4388
+ display: inline-flex;
4389
+ }
4390
+
4391
+ .sp-playlist__button svg,
4392
+ .sp-playlist-skip svg {
4393
+ width: 20px;
4394
+ height: 20px;
4395
+ }
4396
+
4397
+ .sp-playlist__panel {
4398
+ position: absolute;
4399
+ bottom: calc(100% + 8px);
4400
+ right: 0;
4401
+ z-index: 20;
4402
+ display: flex;
4403
+ flex-direction: column;
4404
+ min-width: 240px;
4405
+ max-width: 320px;
4406
+ max-height: 300px;
4407
+ overflow-y: auto;
4408
+ padding: 4px;
4409
+ border-radius: 8px;
4410
+ background: rgba(20, 20, 20, 0.96);
4411
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
4412
+ }
4413
+
4414
+ .sp-playlist__panel[hidden] {
4415
+ display: none;
4416
+ }
4417
+
4418
+ .sp-playlist__item {
4419
+ display: flex;
4420
+ gap: 10px;
4421
+ align-items: flex-start;
4422
+ width: 100%;
4423
+ padding: 8px 10px;
4424
+ border: 0;
4425
+ border-radius: 6px;
4426
+ background: transparent;
4427
+ color: #fff;
4428
+ font: inherit;
4429
+ text-align: left;
4430
+ cursor: pointer;
4431
+ }
4432
+
4433
+ .sp-playlist__item:hover,
4434
+ .sp-playlist__item:focus-visible {
4435
+ background: rgba(255, 255, 255, 0.12);
4436
+ }
4437
+
4438
+ .sp-playlist__item--active {
4439
+ background: rgba(255, 255, 255, 0.08);
4440
+ }
4441
+
4442
+ .sp-playlist__item--active .sp-playlist__title {
4443
+ font-weight: 600;
4444
+ }
4445
+
4446
+ .sp-playlist__position {
4447
+ flex: 0 0 auto;
4448
+ min-width: 18px;
4449
+ color: rgba(255, 255, 255, 0.7);
4450
+ font-variant-numeric: tabular-nums;
4451
+ font-size: 12px;
4452
+ line-height: 18px;
4453
+ }
4454
+
4455
+ .sp-playlist__text {
4456
+ display: flex;
4457
+ flex-direction: column;
4458
+ min-width: 0;
4459
+ }
4460
+
4461
+ .sp-playlist__title {
4462
+ font-size: 13px;
4463
+ line-height: 18px;
4464
+ }
4465
+
4466
+ .sp-playlist__artist {
4467
+ color: rgba(255, 255, 255, 0.6);
4468
+ font-size: 11px;
4469
+ line-height: 16px;
4470
+ }
4471
+
4472
+ @media (max-width: 480px) {
4473
+ .sp-playlist__panel {
4474
+ min-width: 200px;
4475
+ max-width: 76vw;
4476
+ }
4477
+ }
4478
+ `;
4479
+ function injectStyles() {
4480
+ if (typeof document === "undefined" || document.getElementById(STYLE_ID)) {
4481
+ return null;
4482
+ }
4483
+ const el = document.createElement("style");
4484
+ el.id = STYLE_ID;
4485
+ el.textContent = styles;
4486
+ document.head.appendChild(el);
4487
+ return el;
4488
+ }
4223
4489
  var DEFAULT_CONFIG$1 = {
4224
4490
  autoAdvance: true,
4225
4491
  preloadNext: true,
@@ -4409,8 +4675,42 @@ function createPlaylistPlugin(config) {
4409
4675
  api?.emit("playlist:ended", void 0);
4410
4676
  }
4411
4677
  });
4678
+ injectStyles();
4679
+ void import("./hls2.js").then(({ registerControl }) => {
4680
+ const self = plugin;
4681
+ registerControl(
4682
+ "playlist-previous",
4683
+ () => new PlaylistSkipButton(self, "previous")
4684
+ );
4685
+ registerControl("playlist-next", () => new PlaylistSkipButton(self, "next"));
4686
+ registerControl(
4687
+ "playlist",
4688
+ () => new PlaylistPanel(self, {
4689
+ onSelect: (index) => self.play(index)
4690
+ })
4691
+ );
4692
+ }).catch(() => {
4693
+ api?.logger.debug("@scarlett-player/ui not present, playlist controls not registered");
4694
+ });
4695
+ const onKeyDown = (event) => {
4696
+ if (!api || !api.container.contains(document.activeElement)) return;
4697
+ const activeEl = document.activeElement;
4698
+ if (activeEl instanceof HTMLInputElement || activeEl instanceof HTMLTextAreaElement || activeEl instanceof HTMLSelectElement || activeEl?.isContentEditable) {
4699
+ return;
4700
+ }
4701
+ if (event.metaKey || event.ctrlKey || event.altKey) return;
4702
+ if (event.key === "n" || event.key === "N") {
4703
+ event.preventDefault();
4704
+ plugin.next();
4705
+ } else if (event.key === "p" || event.key === "P") {
4706
+ event.preventDefault();
4707
+ plugin.previous();
4708
+ }
4709
+ };
4710
+ document.addEventListener("keydown", onKeyDown);
4412
4711
  api.onDestroy(() => {
4413
4712
  unsubEnded();
4713
+ document.removeEventListener("keydown", onKeyDown);
4414
4714
  if (advanceTimeout) {
4415
4715
  clearTimeout(advanceTimeout);
4416
4716
  advanceTimeout = null;