@discord-player/plugin-host 0.0.0-stage → 8.0.0-dev.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/LICENSE +21 -0
- package/README.md +347 -2
- package/dist/compat/base-extractor.d.ts +88 -0
- package/dist/compat/base-extractor.js +118 -0
- package/dist/compat/base-extractor.js.map +1 -0
- package/dist/compat/extractor-execution-context.d.ts +69 -0
- package/dist/compat/extractor-execution-context.js +121 -0
- package/dist/compat/extractor-execution-context.js.map +1 -0
- package/dist/compat/extractor-plugin.d.ts +1 -0
- package/dist/compat/extractor-plugin.js +126 -0
- package/dist/compat/extractor-plugin.js.map +1 -0
- package/dist/compat/extractor-types.d.ts +49 -0
- package/dist/compat/extractor-types.js +2 -0
- package/dist/compat/extractor-types.js.map +1 -0
- package/dist/compat/extractors.d.ts +4 -0
- package/dist/compat/extractors.js +6 -0
- package/dist/compat/extractors.js.map +1 -0
- package/dist/compat/query-types.d.ts +15 -0
- package/dist/compat/query-types.js +27 -0
- package/dist/compat/query-types.js.map +1 -0
- package/dist/compat/utils.d.ts +28 -0
- package/dist/compat/utils.js +30 -0
- package/dist/compat/utils.js.map +1 -0
- package/dist/errors.d.ts +56 -0
- package/dist/errors.js +63 -0
- package/dist/errors.js.map +1 -0
- package/dist/host.d.ts +76 -0
- package/dist/host.js +317 -0
- package/dist/host.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/legacy-player.d.ts +47 -0
- package/dist/legacy-player.js +2 -0
- package/dist/legacy-player.js.map +1 -0
- package/dist/media.d.ts +15 -0
- package/dist/media.js +193 -0
- package/dist/media.js.map +1 -0
- package/dist/plugins/execution-options.d.ts +25 -0
- package/dist/plugins/execution-options.js +2 -0
- package/dist/plugins/execution-options.js.map +1 -0
- package/dist/plugins/operation-dispatch.d.ts +1 -0
- package/dist/plugins/operation-dispatch.js +111 -0
- package/dist/plugins/operation-dispatch.js.map +1 -0
- package/dist/plugins/plugin-error.d.ts +17 -0
- package/dist/plugins/plugin-error.js +23 -0
- package/dist/plugins/plugin-error.js.map +1 -0
- package/dist/plugins/registry-internals.d.ts +58 -0
- package/dist/plugins/registry-internals.js +2 -0
- package/dist/plugins/registry-internals.js.map +1 -0
- package/dist/plugins/registry.d.ts +66 -0
- package/dist/plugins/registry.js +257 -0
- package/dist/plugins/registry.js.map +1 -0
- package/dist/plugins/stream-resolver.d.ts +1 -0
- package/dist/plugins/stream-resolver.js +98 -0
- package/dist/plugins/stream-resolver.js.map +1 -0
- package/dist/plugins/types.d.ts +170 -0
- package/dist/plugins/types.js +11 -0
- package/dist/plugins/types.js.map +1 -0
- package/dist/protocol.d.ts +28 -0
- package/dist/protocol.js +37 -0
- package/dist/protocol.js.map +1 -0
- package/dist/runtime.d.ts +58 -0
- package/dist/runtime.js +118 -0
- package/dist/runtime.js.map +1 -0
- package/dist/structures/playlist.d.ts +139 -0
- package/dist/structures/playlist.js +117 -0
- package/dist/structures/playlist.js.map +1 -0
- package/dist/structures/search-result.d.ts +30 -0
- package/dist/structures/search-result.js +35 -0
- package/dist/structures/search-result.js.map +1 -0
- package/dist/structures/track-types.d.ts +122 -0
- package/dist/structures/track-types.js +2 -0
- package/dist/structures/track-types.js.map +1 -0
- package/dist/structures/track.d.ts +138 -0
- package/dist/structures/track.js +270 -0
- package/dist/structures/track.js.map +1 -0
- package/dist/types.d.ts +27 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/legacy-serialization-schemas.d.ts +166 -0
- package/dist/utils/legacy-serialization-schemas.js +108 -0
- package/dist/utils/legacy-serialization-schemas.js.map +1 -0
- package/dist/utils/parse-serialized-entity.d.ts +1 -0
- package/dist/utils/parse-serialized-entity.js +43 -0
- package/dist/utils/parse-serialized-entity.js.map +1 -0
- package/dist/utils/safe-event-emitter.d.ts +10 -0
- package/dist/utils/safe-event-emitter.js +10 -0
- package/dist/utils/safe-event-emitter.js.map +1 -0
- package/dist/utils/serde.d.ts +54 -0
- package/dist/utils/serde.js +83 -0
- package/dist/utils/serde.js.map +1 -0
- package/dist/utils/serialization-schemas.d.ts +93 -0
- package/dist/utils/serialization-schemas.js +49 -0
- package/dist/utils/serialization-schemas.js.map +1 -0
- package/dist/utils/serialization.d.ts +36 -0
- package/dist/utils/serialization.js +84 -0
- package/dist/utils/serialization.js.map +1 -0
- package/docs/remedia-contract.md +54 -0
- package/package.json +59 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 Androz2091
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,348 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @discord-player/plugin-host
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Standalone source execution for Discord Player v8, built on the published
|
|
4
|
+
`@remedia/plugin-host`. Requires Node.js 22.18 or newer.
|
|
5
|
+
|
|
6
|
+
```text
|
|
7
|
+
discord-player → @discord-player/plugin-host → @remedia/plugin-host
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
`discord-player` installs this package as a regular dependency and re-exports its
|
|
11
|
+
canonical `BaseExtractor`, `Track`, `Playlist`, `SearchResult`, plugin declarations,
|
|
12
|
+
registry, and serialization classes. Constructor identity and `instanceof` work
|
|
13
|
+
across package imports. No Player, queue manager, Discord client, native audio
|
|
14
|
+
binding, or Remedia audio engine is initialized by importing or creating a host.
|
|
15
|
+
The `@remedia/client` dependency supplies source descriptor types only.
|
|
16
|
+
|
|
17
|
+
The official `@discord-player/extractor` collection is an optional, separate package.
|
|
18
|
+
It is neither bundled nor automatically registered.
|
|
19
|
+
|
|
20
|
+
## Using a host from a bot
|
|
21
|
+
|
|
22
|
+
Normal applications do not need to create an HTTP host. Register sources locally
|
|
23
|
+
as before, or configure standalone deployments once in Player options:
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
const player = new Player({
|
|
27
|
+
// Existing Remedia nodes and gateway send callback:
|
|
28
|
+
...audioOptions,
|
|
29
|
+
sourceHosts: [
|
|
30
|
+
{
|
|
31
|
+
hostId: 'sources-1',
|
|
32
|
+
url: 'https://plugins.example.com',
|
|
33
|
+
token: process.env.SOURCE_TOKEN,
|
|
34
|
+
timeoutMs: 15000,
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const result = await player.search('some song', { hostId: 'sources-1' });
|
|
40
|
+
await player.play(channel, result);
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
This requires `@remedia/client@0.1.0-dev.5` and a matching remote server configured
|
|
44
|
+
with the same `pluginHosts` ID and the `discord-player.source` / `source.stream`
|
|
45
|
+
operation. The bot searches over HTTP and sends an operation descriptor for play
|
|
46
|
+
and automatic crossfade staging. Encoded bytes flow directly from host to audio
|
|
47
|
+
node. Local sources run locally. Embedded nodes cannot play hosted Tracks.
|
|
48
|
+
|
|
49
|
+
See the [complete deployment example](https://discord-player.js.org/docs/plugins/remote-hosts).
|
|
50
|
+
The direct HTTP examples below describe protocol consumers; a bot should use
|
|
51
|
+
`player.search()` and queue playback, without fetching media grants itself.
|
|
52
|
+
|
|
53
|
+
## Registration and direct execution
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import { createSourcePluginHost, SOURCE_PLUGIN_ID } from '@discord-player/plugin-host';
|
|
57
|
+
import { MyExtractor } from './my-extractor.js';
|
|
58
|
+
import { mySourcePlugin } from './my-source-plugin.js';
|
|
59
|
+
|
|
60
|
+
// Local-only handle() usage may omit hostId.
|
|
61
|
+
const host = createSourcePluginHost({ name: 'My Source Host' });
|
|
62
|
+
await host.extractors.register(MyExtractor);
|
|
63
|
+
await host.plugins.register(mySourcePlugin());
|
|
64
|
+
|
|
65
|
+
const response = await host.handle({
|
|
66
|
+
pluginId: SOURCE_PLUGIN_ID,
|
|
67
|
+
operation: 'source.search',
|
|
68
|
+
input: { query: 'example' },
|
|
69
|
+
});
|
|
70
|
+
if (response.handled) console.log(response.result);
|
|
71
|
+
// Or use typed source APIs directly:
|
|
72
|
+
const result = await host.runtime.search('example');
|
|
73
|
+
const track = result.first();
|
|
74
|
+
if (track) {
|
|
75
|
+
const source = await host.plugins.resolveStream(track);
|
|
76
|
+
// Caller owns consumption and destruction of returned Readables.
|
|
77
|
+
console.log(source);
|
|
78
|
+
}
|
|
79
|
+
await host.close();
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Direct execution preserves model and stream references. It needs no public URL,
|
|
83
|
+
listener, or serialization. `host.remedia` exposes the actual generic Remedia host
|
|
84
|
+
for discovery and lifecycle inspection; use `host.handle` and `host.fetch` as the
|
|
85
|
+
application entrypoints, so the boundary conversion remains active.
|
|
86
|
+
|
|
87
|
+
Both Player and standalone execution use `SourcePluginRuntime`. There is one
|
|
88
|
+
registry with the existing `first`, `collect`, `pipe`, and `broadcast` policies,
|
|
89
|
+
provider priorities, protocol matching, bridging, registration cancellation,
|
|
90
|
+
post-stream transforms, and cleanup ownership. Player retains all queue, voice,
|
|
91
|
+
Gateway, crossfade, and playback orchestration.
|
|
92
|
+
|
|
93
|
+
## Hono
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
import { Hono } from 'hono';
|
|
97
|
+
import { serve } from '@hono/node-server';
|
|
98
|
+
import { createSourcePluginHost } from '@discord-player/plugin-host';
|
|
99
|
+
import { MyExtractor } from './my-extractor.js';
|
|
100
|
+
|
|
101
|
+
const host = createSourcePluginHost({ name: 'Sources', hostId: 'sources-eu-1', publicUrl: 'http://localhost:3000' });
|
|
102
|
+
await host.extractors.register(MyExtractor);
|
|
103
|
+
const app = new Hono();
|
|
104
|
+
app.all('*', (c) => host.fetch(c.req.raw));
|
|
105
|
+
const server = serve({ fetch: app.fetch, port: 3000 });
|
|
106
|
+
process.once('SIGTERM', () => {
|
|
107
|
+
server.close();
|
|
108
|
+
void host.close();
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Express
|
|
113
|
+
|
|
114
|
+
Mount Remedia's existing middleware at the origin root before body parsers.
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
import express from 'express';
|
|
118
|
+
import { createNodeMiddleware } from '@remedia/plugin-host/node';
|
|
119
|
+
import { createSourcePluginHost } from '@discord-player/plugin-host';
|
|
120
|
+
import { MyExtractor } from './my-extractor.js';
|
|
121
|
+
|
|
122
|
+
const host = createSourcePluginHost({ name: 'Sources', hostId: 'sources-eu-1', publicUrl: 'http://localhost:3000' });
|
|
123
|
+
await host.extractors.register(MyExtractor);
|
|
124
|
+
const app = express();
|
|
125
|
+
app.use(createNodeMiddleware(host));
|
|
126
|
+
app.use(express.json());
|
|
127
|
+
const server = app.listen(3000);
|
|
128
|
+
process.once('SIGTERM', () => {
|
|
129
|
+
server.close();
|
|
130
|
+
void host.close();
|
|
131
|
+
});
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
No listener starts automatically. Remedia owns HTTP routing, authorization,
|
|
135
|
+
request limits, media tokens, backpressure, stream cancellation, and disposal.
|
|
136
|
+
Configure `token`, `mediaTtlMs`, and `maxMediaTokens` through the host options.
|
|
137
|
+
`publicUrl` is a trusted HTTP(S) origin and is needed only for HTTP media results.
|
|
138
|
+
|
|
139
|
+
## HTTP source contract
|
|
140
|
+
|
|
141
|
+
One aggregate registration, `discord-player.source`, advertises:
|
|
142
|
+
|
|
143
|
+
| Operation | Input | Result |
|
|
144
|
+
| ---------------- | ------------------------------- | ------------------------------------------ |
|
|
145
|
+
| `source.search` | `{ query, options? }` | Metadata through normal provider matching |
|
|
146
|
+
| `source.resolve` | `{ query, pluginId, options? }` | Metadata from an explicit provider |
|
|
147
|
+
| `source.related` | `{ track }` | Related metadata from the track's provider |
|
|
148
|
+
| `source.stream` | `{ track }` | Deferred Remedia media descriptor |
|
|
149
|
+
|
|
150
|
+
`source.stream` includes normal bridge fallback and post-stream transformations;
|
|
151
|
+
internal operation handlers are not separate Remedia registrations. HTTP options
|
|
152
|
+
accept portable plugin/source/searchEngine hints, limit, requester ID, and blocked
|
|
153
|
+
provider IDs. Local HTTP request options, functions and Discord objects do not
|
|
154
|
+
cross the boundary. The transport validates input before source execution.
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
import { SOURCE_PLUGIN_ID, deserializeSourceResult, type PortableSourceResult } from '@discord-player/plugin-host';
|
|
158
|
+
|
|
159
|
+
const response = await fetch('http://localhost:3000/remedia/v1/execute', {
|
|
160
|
+
method: 'POST',
|
|
161
|
+
headers: { 'content-type': 'application/json' },
|
|
162
|
+
body: JSON.stringify({ pluginId: SOURCE_PLUGIN_ID, operation: 'source.search', input: { query: 'example' } }),
|
|
163
|
+
});
|
|
164
|
+
if (!response.ok) throw new Error(`Source search failed: ${response.status}`);
|
|
165
|
+
const search = (await response.json()) as { handled: boolean; result: PortableSourceResult };
|
|
166
|
+
if (!search.handled) throw new Error('Unhandled search');
|
|
167
|
+
const localModels = deserializeSourceResult(search.result);
|
|
168
|
+
console.log(localModels.first());
|
|
169
|
+
|
|
170
|
+
// Send the original portable representation back, including its optional state hint.
|
|
171
|
+
const mediaResponse = await fetch('http://localhost:3000/remedia/v1/execute', {
|
|
172
|
+
method: 'POST',
|
|
173
|
+
headers: { 'content-type': 'application/json' },
|
|
174
|
+
body: JSON.stringify({
|
|
175
|
+
pluginId: SOURCE_PLUGIN_ID,
|
|
176
|
+
operation: 'source.stream',
|
|
177
|
+
input: { track: search.result.tracks[0] },
|
|
178
|
+
}),
|
|
179
|
+
});
|
|
180
|
+
if (!mediaResponse.ok) throw new Error(`Source stream failed: ${mediaResponse.status}`);
|
|
181
|
+
const media = (await mediaResponse.json()) as { handled: boolean; result: { url: string; expiresAtMs: number } };
|
|
182
|
+
if (!media.handled) throw new Error('Unhandled media');
|
|
183
|
+
// Redeem media.result.url before expiresAtMs; this is a short-lived, single-use grant.
|
|
184
|
+
const bytes = await fetch(media.result.url);
|
|
185
|
+
console.log(bytes.body);
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Media resolution starts when the grant is redeemed, using that request's
|
|
189
|
+
cancellation signal rather than the completed JSON request's signal. Readables,
|
|
190
|
+
files, buffers, and upstream HTTP(S) URLs become encoded byte streams before
|
|
191
|
+
post-stream transforms. URLs are proxied inside the host so provider resolution
|
|
192
|
+
and transforms remain there. The adapter never buffers complete media files.
|
|
193
|
+
Direct calls retain native descriptors; HTTP never returns nested Readables.
|
|
194
|
+
|
|
195
|
+
## Portable metadata and process-local state
|
|
196
|
+
|
|
197
|
+
HTTP results contain version 1 Track and Playlist data. `deserializeSourceResult()`
|
|
198
|
+
retains the opaque `sourceState` token on the normal Track and links Playlist
|
|
199
|
+
instances. Pass `serializeSourceTrack(track)` to source operations; no second HTTP
|
|
200
|
+
payload needs to be retained. `serialize()` and `toJSON()` deliberately omit the
|
|
201
|
+
temporary token. Persistence and clones made through persistence reconstruct from
|
|
202
|
+
provider metadata. Ordinary queue/history insertion retains the same Track and its
|
|
203
|
+
token. Host identity persists in `sourceReference.hostId`, separate from
|
|
204
|
+
`sourceReference.pluginId` and the provider's source name/data.
|
|
205
|
+
|
|
206
|
+
HTTP hosts must configure a stable non-secret `hostId`, including deployments
|
|
207
|
+
that do not configure `publicUrl`. Construction with `publicUrl` requires it, and
|
|
208
|
+
`fetch()` rejects HTTP use without it. Local-only `handle()` hosts may omit it and
|
|
209
|
+
receive a generated process identifier that intentionally does not survive restarts. Two hosts
|
|
210
|
+
can use the same provider IDs, but cannot redeem one another's Tracks. The embedded
|
|
211
|
+
registry rejects hosted Tracks when used as local sources. DiscordPlayer routes them
|
|
212
|
+
through configured remote hosts and compatible Remedia nodes instead.
|
|
213
|
+
Do not put endpoints, bearer tokens or credentials in sourceReference.
|
|
214
|
+
|
|
215
|
+
The per-host FIFO cache defaults to 1000 Track states for ten minutes. Previous
|
|
216
|
+
10000/one-hour defaults could retain substantial provider payloads and playlists.
|
|
217
|
+
Periodic cleanup runs even while idle (at most every 30 seconds), with additional
|
|
218
|
+
checks on use, provider lifetime changes and shutdown. Cached snapshots omit
|
|
219
|
+
Playlist links and metadata callbacks so one entry cannot retain the whole result
|
|
220
|
+
through those links. This is a count limit, not a byte memory guarantee: trusted
|
|
221
|
+
providers can still attach large engines. Tune limits for the deployment.
|
|
222
|
+
|
|
223
|
+
Concurrent remote playback receives copied engine/public metadata, never the
|
|
224
|
+
cached Track itself. Non-cloneable engines are reconstructed instead. Copies use
|
|
225
|
+
snapshot metadata rather than callbacks closing over the original Track. Direct
|
|
226
|
+
execution retains original object identity and the caller's ownership contract.
|
|
227
|
+
|
|
228
|
+
After eviction, expiration, or restart, modern plugins receive reconstructed
|
|
229
|
+
Track metadata and their portable sourceReference. Legacy extractors re-run
|
|
230
|
+
`handle()` through their original provider using the public Track URL, select the
|
|
231
|
+
matching URL, and then receive the newly constructed Track in `stream()`. If the
|
|
232
|
+
provider is unavailable or cannot reconstruct the track, execution fails with an
|
|
233
|
+
explicit diagnostic. This means queued tracks can survive cache loss when their
|
|
234
|
+
provider supports URL reconstruction; ephemeral-only sources must be searched
|
|
235
|
+
again. A serialized Track is never a promise of permanent source availability.
|
|
236
|
+
|
|
237
|
+
Raw engines, credentials, functions, live streams, circular objects and legacy
|
|
238
|
+
metadata callbacks are never serialized. Modern metadata and sourceReference
|
|
239
|
+
must follow the existing JSON-safe contract; plugins must put only public source
|
|
240
|
+
identifiers there, never secrets. Invalid metadata fails serialization rather
|
|
241
|
+
than being silently removed. Credentials belong to host-local plugin options.
|
|
242
|
+
|
|
243
|
+
## Legacy compatibility
|
|
244
|
+
|
|
245
|
+
The audited v7 extractors use `this.context.player` for Track/Playlist construction,
|
|
246
|
+
source lookup/bridging, diagnostics and `probeTimeout`. `LegacyPlayerContext` is a
|
|
247
|
+
structural source API, backed by the runtime when standalone and by the actual
|
|
248
|
+
Player when embedded. It supports plugins, extractors, search, debug/error events,
|
|
249
|
+
source options, and legacy constructors. Optional `getRemedia(false)` returns
|
|
250
|
+
null standalone; required audio access and queue/voice operations fail explicitly.
|
|
251
|
+
It never instantiates a dummy Player, queue or voice connection.
|
|
252
|
+
|
|
253
|
+
Legacy raw PCM/Opus override streams remain unsupported: return encoded media.
|
|
254
|
+
Queue history, Discord clients and other process-local playback objects are not
|
|
255
|
+
part of HTTP requests. An extractor that needs arbitrary Player/voice APIs or
|
|
256
|
+
cannot reconstruct private state after expiration requires migration; the source
|
|
257
|
+
compatibility surface does not emulate the whole Player API.
|
|
258
|
+
|
|
259
|
+
## Remedia server integration boundary
|
|
260
|
+
|
|
261
|
+
Remedia dev.5 supports generic host discovery, authenticated operation execution,
|
|
262
|
+
native decoding of single-use grants, and cancellation. Configure the deployment
|
|
263
|
+
in the server's `pluginHosts` list and in the bot's `sourceHosts` list. Installing
|
|
264
|
+
`@remedia/plugin-host@0.1.0-dev.5` alone does not register a host with an audio node.
|
|
265
|
+
|
|
266
|
+
DiscordPlayer sends operation descriptors for playback and staged-next preparation;
|
|
267
|
+
the audio node invokes `source.stream` and consumes encoded bytes directly. The
|
|
268
|
+
bot uses HTTP only for metadata and never redeems a playback grant. Host affinity
|
|
269
|
+
is checked during session selection and before playback/staging. Embedded playback
|
|
270
|
+
continues to execute local registrations and rejects remote-host Tracks.
|
|
271
|
+
|
|
272
|
+
## Validation
|
|
273
|
+
|
|
274
|
+
`pnpm build`, `pnpm typecheck`, `pnpm test`, `pnpm lint`, and `pnpm format:check`
|
|
275
|
+
cover the workspace. Tests include the published optional extractor collection (SDK calls stubbed),
|
|
276
|
+
real HTTP disconnect/shutdown, and real FFmpeg decoding of generated MP3, Ogg/Opus
|
|
277
|
+
and faststart M4A. FFmpeg must be installed; CI installs it explicitly. No personal
|
|
278
|
+
media files or third-party service access are needed. CI builds the host before discord-player,
|
|
279
|
+
includes its dist artifacts, and publishes it before dependent packages using the
|
|
280
|
+
shared release inventory.
|
|
281
|
+
|
|
282
|
+
## Remote media policy
|
|
283
|
+
|
|
284
|
+
`mediaPolicy` denies private, loopback, link-local, reserved and transition IP
|
|
285
|
+
ranges by default. DNS results are checked and the selected address is pinned to
|
|
286
|
+
the connection, preserving TLS hostname verification. Every redirect is checked;
|
|
287
|
+
TLS downgrades and URL user/password credentials are rejected. Optional
|
|
288
|
+
`allowedHosts` restricts exact hostnames. No inbound authorization or provider
|
|
289
|
+
request headers are copied to upstreams. Providers needing custom authorization
|
|
290
|
+
must return an encoded Readable opened by their trusted SDK. Plugins execute
|
|
291
|
+
trusted code and can open their own network/file resources; this policy does not
|
|
292
|
+
sandbox extractor `handle()` or provider-created streams.
|
|
293
|
+
|
|
294
|
+
File descriptors require explicit `allowedFileRoots`. Canonical paths are checked,
|
|
295
|
+
then regular files are opened through owned handles. Authorized roots and parent
|
|
296
|
+
directories must be administrator-owned; concurrent hostile filesystem mutation is
|
|
297
|
+
outside this policy's trust boundary. Default limits are 32 active streams, 512 MiB
|
|
298
|
+
per stream, 30 minutes total, five redirects and 30 seconds upstream socket idle.
|
|
299
|
+
Tune `maxConcurrent`, `maxBytes`, `timeoutMs`, and `allowPrivateNetworks` for trusted
|
|
300
|
+
private deployments. Buffers are already allocated by providers; the host bounds
|
|
301
|
+
output bytes but cannot undo that allocation. Streaming uses backpressure; abort,
|
|
302
|
+
consumer disconnect, byte/deadline errors and host shutdown release resources.
|
|
303
|
+
After headers, errors terminate the body/socket, never masquerade as successful
|
|
304
|
+
JSON responses. Use Remedia's Node middleware to connect network disconnects to
|
|
305
|
+
Fetch cancellation. Configure `token` and HTTPS for remote authorization.
|
|
306
|
+
|
|
307
|
+
Default proxy delivery resolves media on grant redemption. Expired provider
|
|
308
|
+
sources are rejected rather than fetching expired credentials; request a fresh
|
|
309
|
+
`source.stream` grant. Provider expiration survives materialization and stream
|
|
310
|
+
transforms. Grant expiration is a separate deadline managed by Remedia.
|
|
311
|
+
`mediaDelivery: 'direct-url'` enables direct delivery for explicitly declared
|
|
312
|
+
`directUrlProviders` only. Invoking an arbitrary `track.stream` handler may already
|
|
313
|
+
open a Readable, so unknown or mixed-source providers must stay deferred. Declare
|
|
314
|
+
only trusted providers whose handlers return URL metadata without opening media:
|
|
315
|
+
|
|
316
|
+
```ts
|
|
317
|
+
const host = createSourcePluginHost({
|
|
318
|
+
name: 'Sources',
|
|
319
|
+
hostId: 'sources-eu-1',
|
|
320
|
+
publicUrl: 'https://sources.example.com',
|
|
321
|
+
mediaDelivery: 'direct-url',
|
|
322
|
+
directUrlProviders: ['example.url-provider'],
|
|
323
|
+
});
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
These providers resolve URL metadata at `source.stream` invocation, never search.
|
|
327
|
+
The dispatcher checks active, unblocked transformations for the actual stream
|
|
328
|
+
owner. Applicable byte transforms force the existing secure proxy path, and run
|
|
329
|
+
once on redemption. Unrelated providers' transforms do not prevent direct delivery.
|
|
330
|
+
The check repeats after asynchronous URL preparation so newly registered transforms
|
|
331
|
+
cannot be skipped. A declining URL-only handler defers bridge resolution; a handler
|
|
332
|
+
that returns bytes despite its declaration fails explicitly and its stream is
|
|
333
|
+
destroyed, without a retry. Do not list legacy or mixed-result providers.
|
|
334
|
+
|
|
335
|
+
All other providers, including Readables, files and buffers, resolve and materialize
|
|
336
|
+
only when their grant is redeemed. There is no eager byte-source fallback or retained
|
|
337
|
+
live stream in an unredeemed grant. Cancellation, expiration, provider removal and
|
|
338
|
+
shutdown release transport resources; an old grant cannot bind a replacement
|
|
339
|
+
registration. Provider URL expiration remains intact. The consuming node must still
|
|
340
|
+
independently enforce its DNS/redirect policy for direct URLs. Authentication or
|
|
341
|
+
byte transformations requiring the host must use proxy delivery.
|
|
342
|
+
|
|
343
|
+
Single-use media grants reject Range requests. Remedia dev.5 opens these operation
|
|
344
|
+
results as sequential, non-seekable FFmpeg inputs, avoiding default Range probing.
|
|
345
|
+
This does not add random access or turn grant URLs into durable source references.
|
|
346
|
+
See [the Remedia integration contract](docs/remedia-contract.md) for the current
|
|
347
|
+
protocol and validation limits. Native Remedia playback and Discord voice delivery
|
|
348
|
+
remain outside this repository's HTTP/client integration tests.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { Readable } from 'node:stream';
|
|
2
|
+
import type { SourceRuntimeEvents } from '../legacy-player.js';
|
|
3
|
+
import type { Track } from '../structures/track.js';
|
|
4
|
+
import type { Playlist } from '../structures/playlist.js';
|
|
5
|
+
import type { ExtractorExecutionContext } from './extractor-execution-context.js';
|
|
6
|
+
import type { SearchQueryType } from './query-types.js';
|
|
7
|
+
import type { ExtractorInfo, ExtractorSearchContext, ExtractorStreamable, NextFunction } from './extractor-types.js';
|
|
8
|
+
/** Minimal v7 lifecycle and source API, adapted to the source operation registry. */
|
|
9
|
+
export declare class BaseExtractor<Options extends object = object> {
|
|
10
|
+
/** Owning legacy registration context. */
|
|
11
|
+
context: ExtractorExecutionContext;
|
|
12
|
+
/** Original local extractor configuration. */
|
|
13
|
+
options: Options;
|
|
14
|
+
/** Default identifier; subclasses should override. */
|
|
15
|
+
static identifier: string;
|
|
16
|
+
/** Higher priority wins before match confidence. */
|
|
17
|
+
priority: number;
|
|
18
|
+
/** Supported explicit query protocols. */
|
|
19
|
+
protocols: string[];
|
|
20
|
+
/** Create a compatibility instance; use player.extractors.register to own it.
|
|
21
|
+
* @param context Owning compatibility context.
|
|
22
|
+
* @param options Original extractor options.
|
|
23
|
+
*/
|
|
24
|
+
constructor(
|
|
25
|
+
/** Owning legacy registration context. */
|
|
26
|
+
context: ExtractorExecutionContext,
|
|
27
|
+
/** Original local extractor configuration. */
|
|
28
|
+
options?: Options);
|
|
29
|
+
/** Subclass static identifier. */
|
|
30
|
+
get identifier(): string;
|
|
31
|
+
/** Obsolete demux support is unavailable. */
|
|
32
|
+
get supportsDemux(): boolean;
|
|
33
|
+
/** Acquire extractor resources. */
|
|
34
|
+
activate(): Promise<void>;
|
|
35
|
+
/** Release extractor resources. */
|
|
36
|
+
deactivate(): Promise<void>;
|
|
37
|
+
/** Recreate the owned registration with new options.
|
|
38
|
+
* @param options Replacement options.
|
|
39
|
+
*/
|
|
40
|
+
reconfigure(options: Options): Promise<void>;
|
|
41
|
+
/** Test whether this extractor accepts a query.
|
|
42
|
+
* @param query Search text.
|
|
43
|
+
* @param type Query hint.
|
|
44
|
+
*/
|
|
45
|
+
validate(query: string, type?: SearchQueryType | null): Promise<boolean>;
|
|
46
|
+
/** Resolve metadata.
|
|
47
|
+
* @param query Search text.
|
|
48
|
+
* @param context Search hints.
|
|
49
|
+
*/
|
|
50
|
+
handle(query: string, context: ExtractorSearchContext): Promise<ExtractorInfo>;
|
|
51
|
+
/** Resolve media, or decline to enable bridging.
|
|
52
|
+
* @param track Track to stream.
|
|
53
|
+
*/
|
|
54
|
+
stream(track: Track): Promise<ExtractorStreamable | undefined>;
|
|
55
|
+
/** Resolve related metadata.
|
|
56
|
+
* @param track Seed track.
|
|
57
|
+
* @param history Optional queue history; local execution only.
|
|
58
|
+
*/
|
|
59
|
+
getRelatedTracks(track: Track, history?: unknown): Promise<ExtractorInfo>;
|
|
60
|
+
/** Explicit fallback for another source.
|
|
61
|
+
* @param track Track needing a stream.
|
|
62
|
+
* @param sourceExtractor Original extractor, if legacy.
|
|
63
|
+
*/
|
|
64
|
+
bridge(track: Track, sourceExtractor: BaseExtractor<any> | null): Promise<ExtractorStreamable | null>;
|
|
65
|
+
/** Transform an encoded local Readable. Raw codec overrides are unsupported.
|
|
66
|
+
* @param stream Encoded input.
|
|
67
|
+
* @param next Completion callback.
|
|
68
|
+
*/
|
|
69
|
+
handlePostStream(stream: Readable, next: NextFunction): void;
|
|
70
|
+
/** Build an anonymous bridge query.
|
|
71
|
+
* @param track Track metadata.
|
|
72
|
+
*/
|
|
73
|
+
createBridgeQuery(track: Track): string;
|
|
74
|
+
/** Construct a metadata response.
|
|
75
|
+
* @param playlist Optional playlist.
|
|
76
|
+
* @param tracks Ordered tracks, defaulting to playlist entries.
|
|
77
|
+
*/
|
|
78
|
+
createResponse(playlist?: Playlist | null, tracks?: Track[]): ExtractorInfo;
|
|
79
|
+
/** Publish a diagnostic.
|
|
80
|
+
* @param message Diagnostic text.
|
|
81
|
+
*/
|
|
82
|
+
debug(message: string): void;
|
|
83
|
+
/** Forward supported Player events.
|
|
84
|
+
* @param event Player event.
|
|
85
|
+
* @param args Event arguments.
|
|
86
|
+
*/
|
|
87
|
+
emit<K extends keyof SourceRuntimeEvents>(event: K, ...args: SourceRuntimeEvents[K]): boolean;
|
|
88
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/** Minimal v7 lifecycle and source API, adapted to the source operation registry. */
|
|
2
|
+
export class BaseExtractor {
|
|
3
|
+
context;
|
|
4
|
+
options;
|
|
5
|
+
/** Default identifier; subclasses should override. */
|
|
6
|
+
static identifier = 'org.example.extractor';
|
|
7
|
+
/** Higher priority wins before match confidence. */
|
|
8
|
+
priority = 1;
|
|
9
|
+
/** Supported explicit query protocols. */
|
|
10
|
+
protocols = [];
|
|
11
|
+
/** Create a compatibility instance; use player.extractors.register to own it.
|
|
12
|
+
* @param context Owning compatibility context.
|
|
13
|
+
* @param options Original extractor options.
|
|
14
|
+
*/
|
|
15
|
+
constructor(
|
|
16
|
+
/** Owning legacy registration context. */
|
|
17
|
+
context,
|
|
18
|
+
/** Original local extractor configuration. */
|
|
19
|
+
options = {}) {
|
|
20
|
+
this.context = context;
|
|
21
|
+
this.options = options;
|
|
22
|
+
}
|
|
23
|
+
/** Subclass static identifier. */
|
|
24
|
+
get identifier() {
|
|
25
|
+
return this.constructor.identifier;
|
|
26
|
+
}
|
|
27
|
+
/** Obsolete demux support is unavailable. */
|
|
28
|
+
get supportsDemux() {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
/** Acquire extractor resources. */
|
|
32
|
+
async activate() { }
|
|
33
|
+
/** Release extractor resources. */
|
|
34
|
+
async deactivate() { }
|
|
35
|
+
/** Recreate the owned registration with new options.
|
|
36
|
+
* @param options Replacement options.
|
|
37
|
+
*/
|
|
38
|
+
async reconfigure(options) {
|
|
39
|
+
await this.context.reconfigure(this.identifier, options);
|
|
40
|
+
}
|
|
41
|
+
/** Test whether this extractor accepts a query.
|
|
42
|
+
* @param query Search text.
|
|
43
|
+
* @param type Query hint.
|
|
44
|
+
*/
|
|
45
|
+
async validate(query, type) {
|
|
46
|
+
void query;
|
|
47
|
+
void type;
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
/** Resolve metadata.
|
|
51
|
+
* @param query Search text.
|
|
52
|
+
* @param context Search hints.
|
|
53
|
+
*/
|
|
54
|
+
async handle(query, context) {
|
|
55
|
+
void query;
|
|
56
|
+
void context;
|
|
57
|
+
return this.createResponse();
|
|
58
|
+
}
|
|
59
|
+
/** Resolve media, or decline to enable bridging.
|
|
60
|
+
* @param track Track to stream.
|
|
61
|
+
*/
|
|
62
|
+
async stream(track) {
|
|
63
|
+
void track;
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
/** Resolve related metadata.
|
|
67
|
+
* @param track Seed track.
|
|
68
|
+
* @param history Optional queue history; local execution only.
|
|
69
|
+
*/
|
|
70
|
+
async getRelatedTracks(track, history) {
|
|
71
|
+
void track;
|
|
72
|
+
void history;
|
|
73
|
+
return this.createResponse();
|
|
74
|
+
}
|
|
75
|
+
/** Explicit fallback for another source.
|
|
76
|
+
* @param track Track needing a stream.
|
|
77
|
+
* @param sourceExtractor Original extractor, if legacy.
|
|
78
|
+
*/
|
|
79
|
+
async bridge(track, sourceExtractor) {
|
|
80
|
+
void track;
|
|
81
|
+
void sourceExtractor;
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
/** Transform an encoded local Readable. Raw codec overrides are unsupported.
|
|
85
|
+
* @param stream Encoded input.
|
|
86
|
+
* @param next Completion callback.
|
|
87
|
+
*/
|
|
88
|
+
handlePostStream(stream, next) {
|
|
89
|
+
next(null, stream);
|
|
90
|
+
}
|
|
91
|
+
/** Build an anonymous bridge query.
|
|
92
|
+
* @param track Track metadata.
|
|
93
|
+
*/
|
|
94
|
+
createBridgeQuery(track) {
|
|
95
|
+
return `${track.title} by ${track.author}`;
|
|
96
|
+
}
|
|
97
|
+
/** Construct a metadata response.
|
|
98
|
+
* @param playlist Optional playlist.
|
|
99
|
+
* @param tracks Ordered tracks, defaulting to playlist entries.
|
|
100
|
+
*/
|
|
101
|
+
createResponse(playlist = null, tracks = playlist?.tracks ?? []) {
|
|
102
|
+
return { playlist, tracks };
|
|
103
|
+
}
|
|
104
|
+
/** Publish a diagnostic.
|
|
105
|
+
* @param message Diagnostic text.
|
|
106
|
+
*/
|
|
107
|
+
debug(message) {
|
|
108
|
+
this.context.player.debug(message);
|
|
109
|
+
}
|
|
110
|
+
/** Forward supported Player events.
|
|
111
|
+
* @param event Player event.
|
|
112
|
+
* @param args Event arguments.
|
|
113
|
+
*/
|
|
114
|
+
emit(event, ...args) {
|
|
115
|
+
return this.context.player.emit(event, ...args);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=base-extractor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base-extractor.js","sourceRoot":"","sources":["../../src/compat/base-extractor.ts"],"names":[],"mappings":"AAQA,qFAAqF;AACrF,MAAM,OAAO,aAAa;IAgBf,OAAO;IAEP,OAAO;IAjBhB,sDAAsD;IAC/C,MAAM,CAAC,UAAU,GAAG,uBAAuB,CAAC;IAEnD,oDAAoD;IAC7C,QAAQ,GAAG,CAAC,CAAC;IAEpB,0CAA0C;IACnC,SAAS,GAAa,EAAE,CAAC;IAEhC;;;OAGG;IACH;IACE,0CAA0C;IACnC,OAAkC;IACzC,8CAA8C;IACvC,OAAO,GAAY,EAAa;uBAFhC,OAAO;uBAEP,OAAO;IACb,CAAC;IAEJ,kCAAkC;IAClC,IAAW,UAAU;QACnB,OAAQ,IAAI,CAAC,WAAoC,CAAC,UAAU,CAAC;IAC/D,CAAC;IAED,6CAA6C;IAC7C,IAAW,aAAa;QACtB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,mCAAmC;IAC5B,KAAK,CAAC,QAAQ,KAAmB,CAAC;IAEzC,mCAAmC;IAC5B,KAAK,CAAC,UAAU,KAAmB,CAAC;IAE3C;;OAEG;IACI,KAAK,CAAC,WAAW,CAAC,OAAgB;QACvC,MAAM,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IAC3D,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,QAAQ,CAAC,KAAa,EAAE,IAA6B;QAChE,KAAK,KAAK,CAAC;QACX,KAAK,IAAI,CAAC;QAEV,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,MAAM,CAAC,KAAa,EAAE,OAA+B;QAChE,KAAK,KAAK,CAAC;QACX,KAAK,OAAO,CAAC;QAEb,OAAO,IAAI,CAAC,cAAc,EAAE,CAAC;IAC/B,CAAC;IAED;;OAEG;IACI,KAAK,CAAC,MAAM,CAAC,KAAY;QAC9B,KAAK,KAAK,CAAC;QAEX,OAAO,SAAS,CAAC;IACnB,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,gBAAgB,CAAC,KAAY,EAAE,OAAiB;QAC3D,KAAK,KAAK,CAAC;QACX,KAAK,OAAO,CAAC;QAEb,OAAO,IAAI,CAAC,cAAc,EAAE,CAAC;IAC/B,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,MAAM,CAAC,KAAY,EAAE,eAA0C;QAC1E,KAAK,KAAK,CAAC;QACX,KAAK,eAAe,CAAC;QAErB,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;OAGG;IACI,gBAAgB,CAAC,MAAgB,EAAE,IAAkB;QAC1D,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACrB,CAAC;IAED;;OAEG;IACI,iBAAiB,CAAC,KAAY;QACnC,OAAO,GAAG,KAAK,CAAC,KAAK,OAAO,KAAK,CAAC,MAAM,EAAE,CAAC;IAC7C,CAAC;IAED;;;OAGG;IACI,cAAc,CAAC,QAAQ,GAAoB,IAAI,EAAE,MAAM,GAAY,QAAQ,EAAE,MAAM,IAAI,EAAE;QAC9F,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;IAC9B,CAAC;IAED;;OAEG;IACI,KAAK,CAAC,OAAe;QAC1B,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACrC,CAAC;IAED;;;OAGG;IACI,IAAI,CAAsC,KAAQ,EAAE,GAAG,IAA4B;QACxF,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,GAAI,IAAc,CAAC,CAAC;IAC7D,CAAC;CACF"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { LegacyPlayerContext } from '../legacy-player.js';
|
|
2
|
+
import type { Track } from '../structures/track.js';
|
|
3
|
+
import type { SourceStream } from '../plugins/types.js';
|
|
4
|
+
import { BaseExtractor } from './base-extractor.js';
|
|
5
|
+
import type { ExtractorConstructor } from './extractor-types.js';
|
|
6
|
+
/** Small legacy ownership adapter; all execution goes through player.plugins. */
|
|
7
|
+
export declare class ExtractorExecutionContext {
|
|
8
|
+
/** Player owning legacy registrations. */
|
|
9
|
+
readonly player: LegacyPlayerContext;
|
|
10
|
+
private bridgeAttempts;
|
|
11
|
+
private instances;
|
|
12
|
+
/** Read-only view of registered legacy instances. */
|
|
13
|
+
get store(): ReadonlyMap<string, BaseExtractor<any>>;
|
|
14
|
+
/** Number of legacy registrations. */
|
|
15
|
+
get size(): number;
|
|
16
|
+
/** Create an adapter owned by one Player.
|
|
17
|
+
* @param player Owning Player.
|
|
18
|
+
*/
|
|
19
|
+
constructor(
|
|
20
|
+
/** Player owning legacy registrations. */
|
|
21
|
+
player: LegacyPlayerContext);
|
|
22
|
+
/** Look up a legacy registration.
|
|
23
|
+
* @param id Extractor identifier.
|
|
24
|
+
*/
|
|
25
|
+
get(id: string): BaseExtractor<any> | undefined;
|
|
26
|
+
/** Test legacy registration membership.
|
|
27
|
+
* @param id Extractor identifier.
|
|
28
|
+
*/
|
|
29
|
+
isRegistered(id: string): boolean;
|
|
30
|
+
/** Check the Player block list.
|
|
31
|
+
* @param id Extractor identifier.
|
|
32
|
+
*/
|
|
33
|
+
isDisabled(id: string): boolean;
|
|
34
|
+
/** Check whether a source is allowed.
|
|
35
|
+
* @param id Extractor identifier.
|
|
36
|
+
*/
|
|
37
|
+
isEnabled(id: string): boolean;
|
|
38
|
+
/** Register a legacy class through a source plugin.
|
|
39
|
+
* @param Extractor Legacy BaseExtractor subclass constructor.
|
|
40
|
+
* @param options Original options passed to the instance.
|
|
41
|
+
* @returns The owned compatibility instance.
|
|
42
|
+
*/
|
|
43
|
+
register<C extends ExtractorConstructor>(Extractor: C, ...args: {} extends ConstructorParameters<C>[1] ? [options?: ConstructorParameters<C>[1]] : [options: ConstructorParameters<C>[1]]): Promise<InstanceType<C>>;
|
|
44
|
+
/** Remove an extractor and its source subscriptions.
|
|
45
|
+
* @param extractor ID or owned instance.
|
|
46
|
+
*/
|
|
47
|
+
unregister(extractor: string | BaseExtractor<any>): Promise<boolean>;
|
|
48
|
+
/** Remove all legacy registrations. */
|
|
49
|
+
unregisterAll(): Promise<void>;
|
|
50
|
+
/** Replace an instance through the same registration path.
|
|
51
|
+
* @param id Legacy ID.
|
|
52
|
+
* @param options Replacement options.
|
|
53
|
+
*/
|
|
54
|
+
reconfigure(id: string, options: object): Promise<BaseExtractor<any>>;
|
|
55
|
+
/** Register a bundle sequentially with deterministic precedence.
|
|
56
|
+
* @param bundle Legacy constructors.
|
|
57
|
+
* @param options Options keyed by identifier.
|
|
58
|
+
*/
|
|
59
|
+
loadMulti(bundle: readonly ExtractorConstructor[], options?: Record<string, object>): Promise<void>;
|
|
60
|
+
/** Request explicit bridge fallback using the shared registry.
|
|
61
|
+
* @param track Track needing a bridge.
|
|
62
|
+
* @param sourceExtractor Original provider.
|
|
63
|
+
*/
|
|
64
|
+
requestBridge(track: Track, sourceExtractor?: BaseExtractor<any> | null): Promise<{
|
|
65
|
+
result: SourceStream;
|
|
66
|
+
error: null;
|
|
67
|
+
extractor: BaseExtractor<any> | null;
|
|
68
|
+
}>;
|
|
69
|
+
}
|