npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ziplayer/adapters

v0.0.1

Published

Third-party plugin adapter bridge for ZiPlayer – wrap discord-player, DisTube, youtube-dl-exec, and more.

Readme

@ziplayer/adapters

Bridge package that lets you register any third-party extractor in ZiPlayer without rewriting it.

Supported ecosystems out of the box:

| Source | Accepted shapes | Detection | | --------------------------------------------- | -------------------------- | --------------------------------------------------- | | ZiPlayer BasePlugin | instance or class | canHandle + search + getStream → pass-through | | discord-player single extractor | instance or class | handle / activate / stream | | discord-player DefaultExtractors | array of extractor classes | every element is a DP extractor constructor | | discord-player extractor container | object with .extractors | .extractors is a non-empty Map or Array | | DisTube plugin | instance or class | resolve / searchSong / getStreamURL | | Generic (youtube-dl-exec, yt-dlp-wrap, …) | plain object | getInfo / download | | Array of any of the above | mixed array | fan-out MultiAdapter |


Installation

npm install @ziplayer/adapters ziplayer

Quick Start

import { PlayerManager } from "ziplayer";
import { YouTubePlugin } from "@ziplayer/plugin";
import { provide } from "@ziplayer/adapters";

// ── discord-player ──────────────────────────────────────────────
import { DefaultExtractors, SoundCloudExtractor } from "@discord-player/extractor";

// ── DisTube ─────────────────────────────────────────────────────
import { SoundCloudPlugin } from "@distube/soundcloud";
import { SpotifyPlugin } from "@distube/spotify";

// ── Generic (youtube-dl-exec) ────────────────────────────────────
import youtubeDl from "youtube-dl-exec";

const manager = new PlayerManager({
	plugins: [
		// Native ZiPlayer plugin instance – returned as-is
		new YouTubePlugin({}),

		// discord-player DefaultExtractors (array of extractor classes)
		provide(DefaultExtractors),

		// Single discord-player extractor instance with priority override
		provide(new SoundCloudExtractor(), { priority: 8 }),

		// DisTube plugin instances
		provide(new SoundCloudPlugin()),
		provide(new SpotifyPlugin({ api: { clientId: "...", clientSecret: "..." } })),

		// Generic extractor – needs getInfo() and/or download()
		provide({
			name: "yt-dlp",
			getInfo: (url) => youtubeDl(url, { dumpSingleJson: true, noWarnings: true }),
			download: (url) => youtubeDl.raw(url, { output: "-" }),
		}),

		// Array shorthand – each item wrapped individually, bundled as MultiAdapter
		provide([new SoundCloudPlugin(), new SpotifyPlugin()]),
	],
	enableStatsCollection: true,
});

API

provide(plugin, options?)

The only function you need. Auto-detects the plugin type and returns a BasePlugin compatible with ZiPlayer.

import { provide } from "@ziplayer/adapters";
import type { ProvideOptions } from "@ziplayer/adapters";

const adapted = provide(thirdPartyPlugin, {
	priority: 10, // override ZiPlayer priority (higher = tried first)
	name: "my-ext", // override plugin name (has no effect on native ZiPlayer plugins)
});

All accepted plugin shapes:

// ZiPlayer plugin instance → pass-through
provide(new YouTubePlugin({}));

// ZiPlayer plugin class → auto-instantiated, then pass-through
provide(YouTubePlugin);

// discord-player DefaultExtractors (array of extractor classes)
provide(DefaultExtractors);

// discord-player extractor container (object with .extractors Map or Array)
provide(myExtractorContainer);

// discord-player single extractor class → auto-instantiated
provide(SoundCloudExtractor);

// discord-player single extractor instance
provide(new SoundCloudExtractor());

// DisTube plugin class → auto-instantiated
provide(SoundCloudPlugin);

// DisTube plugin instance
provide(new SoundCloudPlugin());

// Generic object with getInfo() and/or download()
provide({
	name: "custom",
	getInfo: async (url) => ({
		/* ... */
	}),
});

// Array of any of the above → MultiAdapter
// (single-element array returns the wrapped item directly)
provide([new SoundCloudPlugin(), new SpotifyPlugin()]);

Adapter Classes (advanced)

You can import the individual adapters for fine-grained control:

import {
	DiscordPlayerExtractorAdapter, // single discord-player extractor
	DiscordPlayerContainerAdapter, // DefaultExtractors / .extractors container
	DistubePluginAdapter, // DisTube plugin
	GenericExtractorAdapter, // youtube-dl-exec / yt-dlp-wrap / custom
	MultiAdapter, // fan-out across multiple adapters
} from "@ziplayer/adapters";

const adapter = new DistubePluginAdapter(new SoundCloudPlugin());
adapter.priority = 15;

const manager = new PlayerManager({
	plugins: [adapter],
});

Writing a custom adapter

If provide() doesn't recognise your extractor, implement BasePlugin directly:

import { BasePlugin, Track, SearchResult, StreamInfo } from "ziplayer";
import MyLib from "my-audio-lib";

export class MyLibAdapter extends BasePlugin {
	name = "mylib";
	version = "1.0.0";
	priority = 5;

	canHandle(query: string): boolean {
		return MyLib.supports(query);
	}

	async search(query: string, requestedBy: string): Promise<SearchResult> {
		const results = await MyLib.search(query);
		return {
			tracks: results.map((r) => ({
				id: r.id,
				title: r.name,
				url: r.url,
				duration: r.duration * 1000,
				thumbnail: r.image,
				requestedBy,
				source: this.name,
				metadata: { original: r },
			})),
		};
	}

	async getStream(track: Track): Promise<StreamInfo> {
		const stream = await MyLib.stream(track.url);
		return { stream, type: "arbitrary" };
	}
}

Then use it directly — no provide() needed:

new PlayerManager({
	plugins: [new MyLibAdapter()],
});

How detection works

provide() evaluates the input in this exact order, stopping at the first match:

 1. Array where every element is a discord-player extractor class
      → DiscordPlayerContainerAdapter

 2. Any other non-empty array
      → each element wrapped recursively; single-element array returns
        the wrapper directly, otherwise bundled as MultiAdapter

 3. Class whose prototype has canHandle + search + getStream   (ZiPlayer)
      → auto-instantiated → pass-through

 4. Class whose prototype has resolve / searchSong / getStreamURL  (DisTube)
      → auto-instantiated → DistubePluginAdapter

 5. Class whose prototype has handle / activate / stream  (discord-player)
      → auto-instantiated → DiscordPlayerExtractorAdapter

 6. Any other constructor function
      → auto-instantiated → re-evaluated from step 1

 7. Object whose .extractors property is a non-empty Map or Array
      → DiscordPlayerContainerAdapter

 8. Object with canHandle + search + getStream   (ZiPlayer instance)
      → pass-through

 9. Object with resolve / searchSong / getStreamURL  (DisTube instance)
      → DistubePluginAdapter

10. Object with handle / activate / stream  (discord-player extractor instance)
      → DiscordPlayerExtractorAdapter

11. Object with getInfo() or download()   (generic)
      → GenericExtractorAdapter

12. else → throws a descriptive error

Why DisTube is checked before discord-player at the instance level (steps 9–10): Both ecosystems can expose a validate() method. Checking DisTube's distinct shape (resolve / searchSong / getStreamURL) first prevents mis-routing DisTube plugins into the discord-player extractor adapter.