ziplayer 0.3.3 → 0.3.4
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/AGENTS.md +717 -653
- package/README.md +658 -639
- package/dist/extensions/BaseExtension.d.ts +10 -1
- package/dist/extensions/BaseExtension.d.ts.map +1 -1
- package/dist/extensions/BaseExtension.js +27 -1
- package/dist/extensions/BaseExtension.js.map +1 -1
- package/dist/extensions/index.d.ts.map +1 -1
- package/dist/extensions/index.js +24 -6
- package/dist/extensions/index.js.map +1 -1
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/index.js +104 -50
- package/dist/plugins/index.js.map +1 -1
- package/dist/structures/Player.d.ts +74 -43
- package/dist/structures/Player.d.ts.map +1 -1
- package/dist/structures/Player.js +440 -114
- package/dist/structures/Player.js.map +1 -1
- package/dist/structures/PlayerManager.d.ts +41 -6
- package/dist/structures/PlayerManager.d.ts.map +1 -1
- package/dist/structures/PlayerManager.js +94 -125
- package/dist/structures/PlayerManager.js.map +1 -1
- package/dist/types/extension.d.ts +3 -0
- package/dist/types/extension.d.ts.map +1 -1
- package/dist/types/index.d.ts +38 -11
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +7 -0
- package/dist/types/index.js.map +1 -1
- package/package.json +1 -1
- package/src/extensions/BaseExtension.ts +31 -1
- package/src/extensions/index.ts +30 -7
- package/src/plugins/index.ts +136 -53
- package/src/structures/Player.ts +2937 -2544
- package/src/structures/PlayerManager.ts +916 -955
- package/src/structures/Queue.ts +621 -621
- package/src/types/extension.ts +3 -0
- package/src/types/index.ts +43 -11
package/src/extensions/index.ts
CHANGED
|
@@ -61,8 +61,13 @@ export class ExtensionManager {
|
|
|
61
61
|
this.streamCache = new Map();
|
|
62
62
|
this.pendingSearches = new Map();
|
|
63
63
|
this.pendingStreams = new Map();
|
|
64
|
-
this.extensionContext = Object.freeze({
|
|
65
|
-
|
|
64
|
+
this.extensionContext = Object.freeze({
|
|
65
|
+
player,
|
|
66
|
+
manager,
|
|
67
|
+
playNext: () => (player as any).playNext?.(),
|
|
68
|
+
skip: () => (player as any).skip?.(),
|
|
69
|
+
emit: (event: string, ...args: any[]) => player.emit(event as any, ...args),
|
|
70
|
+
});
|
|
66
71
|
// Auto-cleanup caches periodically
|
|
67
72
|
setInterval(() => this.cleanupCaches(), 5 * 60 * 1000);
|
|
68
73
|
}
|
|
@@ -314,7 +319,10 @@ export class ExtensionManager {
|
|
|
314
319
|
|
|
315
320
|
// Check cache first
|
|
316
321
|
const cached = this.getCachedStream(track);
|
|
317
|
-
if (cached)
|
|
322
|
+
if (cached) {
|
|
323
|
+
this.debug(`[Cache] Stream hit for: ${track.title}`);
|
|
324
|
+
return cached;
|
|
325
|
+
}
|
|
318
326
|
|
|
319
327
|
// Deduplicate concurrent requests
|
|
320
328
|
const cacheKey = this.getCacheKey("stream", track.url || track.id || track.title);
|
|
@@ -333,16 +341,31 @@ export class ExtensionManager {
|
|
|
333
341
|
if (typeof hook !== "function") continue;
|
|
334
342
|
|
|
335
343
|
try {
|
|
344
|
+
this.debug(`Trying extension ${extension.name} for stream: ${track.title}`);
|
|
336
345
|
const result = await Promise.resolve(hook.call(extension, this.extensionContext, request));
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
346
|
+
|
|
347
|
+
if (result) {
|
|
348
|
+
const isRemote = (result as StreamInfo).remote;
|
|
349
|
+
const hasStream = !!(result as StreamInfo).stream;
|
|
350
|
+
const hasHandle = !!(result as StreamInfo).handle;
|
|
351
|
+
|
|
352
|
+
this.debug(
|
|
353
|
+
`Extension ${extension.name} returned stream for ${track.title}: remote=${isRemote}, hasStream=${hasStream}, hasHandle=${hasHandle}`,
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
if (hasStream || hasHandle) {
|
|
357
|
+
// Only cache if it's a reusable stream (not remote with handle)
|
|
358
|
+
if (!isRemote) {
|
|
359
|
+
this.setCachedStream(track, result as StreamInfo);
|
|
360
|
+
}
|
|
361
|
+
return result as StreamInfo;
|
|
362
|
+
}
|
|
341
363
|
}
|
|
342
364
|
} catch (err) {
|
|
343
365
|
this.debug(`Extension ${extension.name} provideStream error:`, err);
|
|
344
366
|
}
|
|
345
367
|
}
|
|
368
|
+
this.debug(`No extension provided stream for: ${track.title}`);
|
|
346
369
|
return null;
|
|
347
370
|
})();
|
|
348
371
|
|
package/src/plugins/index.ts
CHANGED
|
@@ -704,43 +704,123 @@ export class PluginManager {
|
|
|
704
704
|
}
|
|
705
705
|
|
|
706
706
|
private async getStreamInternal(track: Track, primary: BasePlugin): Promise<StreamInfo | null> {
|
|
707
|
+
// Reuse existing stream from StreamManager
|
|
707
708
|
if (this.streamManager) {
|
|
708
709
|
const existingStream = this.streamManager.getStreamByTrack(track.id || track.title);
|
|
710
|
+
|
|
709
711
|
if (existingStream) {
|
|
710
712
|
this.debug(`[Stream] Using existing stream from manager`);
|
|
711
|
-
|
|
713
|
+
|
|
714
|
+
return {
|
|
715
|
+
stream: existingStream,
|
|
716
|
+
type: "arbitrary",
|
|
717
|
+
};
|
|
712
718
|
}
|
|
713
719
|
}
|
|
714
720
|
|
|
715
721
|
const timeoutMs = this.options.extractorTimeout ?? 50000;
|
|
716
722
|
|
|
723
|
+
// Cache
|
|
717
724
|
const cached = this.getCachedStream(track);
|
|
718
|
-
if (cached) return cached;
|
|
719
725
|
|
|
720
|
-
|
|
721
|
-
this.debug(`[Stream]
|
|
726
|
+
if (cached) {
|
|
727
|
+
this.debug(`[Stream] Using cached stream for: ${track.title}`);
|
|
728
|
+
return cached;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
/**
|
|
732
|
+
* Try resolve stream from plugin
|
|
733
|
+
* Flow:
|
|
734
|
+
* 1. plugin.getStream()
|
|
735
|
+
* 2. validate stream
|
|
736
|
+
* 3. if failed -> plugin.getFallback()
|
|
737
|
+
*/
|
|
738
|
+
const tryPlugin = async (
|
|
739
|
+
plugin: BasePlugin,
|
|
740
|
+
isPrimary: boolean = false,
|
|
741
|
+
): Promise<{ result: StreamInfo | null; similarity: number }> => {
|
|
722
742
|
const controller = new AbortController();
|
|
723
|
-
const result = await withTimeout(
|
|
724
|
-
primary.getStream(track, controller.signal),
|
|
725
|
-
timeoutMs,
|
|
726
|
-
`Primary timeout: ${primary.name}`,
|
|
727
|
-
);
|
|
728
743
|
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
744
|
+
let result: StreamInfo | null = null;
|
|
745
|
+
|
|
746
|
+
// =========================================================
|
|
747
|
+
// 1. TRY DIRECT STREAM
|
|
748
|
+
// =========================================================
|
|
749
|
+
if (plugin.getStream) {
|
|
750
|
+
try {
|
|
751
|
+
this.debug(`[Stream] ${plugin.name} trying direct stream`);
|
|
752
|
+
|
|
753
|
+
result = await withTimeout(plugin.getStream(track, controller.signal), timeoutMs, `${plugin.name} getStream timeout`);
|
|
754
|
+
|
|
755
|
+
if (result?.stream) {
|
|
756
|
+
const valid = await this.validateStreamMatchesTrack(result, track);
|
|
757
|
+
|
|
758
|
+
if (valid) {
|
|
759
|
+
this.debug(`[Stream] ${plugin.name} direct stream success`);
|
|
760
|
+
|
|
761
|
+
return {
|
|
762
|
+
result,
|
|
763
|
+
similarity: 1,
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
this.debug(`[Stream] ${plugin.name} returned invalid stream`);
|
|
768
|
+
} else {
|
|
769
|
+
this.debug(`[Stream] ${plugin.name} no direct stream returned`);
|
|
770
|
+
}
|
|
771
|
+
} catch (error) {
|
|
772
|
+
this.debug(`[Stream] ${plugin.name} getStream failed:`, error instanceof Error ? error.message : error);
|
|
735
773
|
}
|
|
736
|
-
this.debug(`[Stream] Stream validation failed - wrong track returned`);
|
|
737
774
|
}
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
775
|
+
|
|
776
|
+
// =========================================================
|
|
777
|
+
// 2. TRY FALLBACK SEARCH
|
|
778
|
+
// =========================================================
|
|
779
|
+
if (plugin.getFallback) {
|
|
780
|
+
try {
|
|
781
|
+
this.debug(`[Stream] ${plugin.name} trying fallback resolver`);
|
|
782
|
+
|
|
783
|
+
result = await withTimeout(plugin.getFallback(track, controller.signal), timeoutMs, `${plugin.name} fallback timeout`);
|
|
784
|
+
|
|
785
|
+
if (result?.stream) {
|
|
786
|
+
const similarity = this.calculateTrackSimilarity(track, {
|
|
787
|
+
title: result.metadata?.title || result.metadata?.originalTitle || track.title,
|
|
788
|
+
});
|
|
789
|
+
|
|
790
|
+
this.debug(`[Stream] ${plugin.name} fallback success (${similarity})`);
|
|
791
|
+
|
|
792
|
+
return {
|
|
793
|
+
result,
|
|
794
|
+
similarity,
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
this.debug(`[Stream] ${plugin.name} fallback returned no stream`);
|
|
799
|
+
} catch (error) {
|
|
800
|
+
this.debug(`[Stream] ${plugin.name} fallback failed:`, error instanceof Error ? error.message : error);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
return {
|
|
805
|
+
result: null,
|
|
806
|
+
similarity: 0,
|
|
807
|
+
};
|
|
808
|
+
};
|
|
809
|
+
|
|
810
|
+
// =========================================================
|
|
811
|
+
// PRIMARY PLUGIN
|
|
812
|
+
// =========================================================
|
|
813
|
+
const primaryResult = await tryPlugin(primary, true);
|
|
814
|
+
|
|
815
|
+
if (primaryResult.result?.stream) {
|
|
816
|
+
this.setCachedStream(track, primaryResult.result);
|
|
817
|
+
|
|
818
|
+
return primaryResult.result;
|
|
741
819
|
}
|
|
742
820
|
|
|
743
|
-
//
|
|
821
|
+
// =========================================================
|
|
822
|
+
// FALLBACK PLUGINS
|
|
823
|
+
// =========================================================
|
|
744
824
|
const fallbackPlugins = this.getAll()
|
|
745
825
|
.filter((p) => p !== primary && p.name !== primary.name)
|
|
746
826
|
.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
@@ -752,61 +832,64 @@ export class PluginManager {
|
|
|
752
832
|
|
|
753
833
|
this.debug(`[Stream] Trying ${fallbackPlugins.length} fallback plugins`);
|
|
754
834
|
|
|
755
|
-
const validResults: Array<{
|
|
835
|
+
const validResults: Array<{
|
|
836
|
+
plugin: string;
|
|
837
|
+
streamInfo: StreamInfo;
|
|
838
|
+
score: number;
|
|
839
|
+
}> = [];
|
|
756
840
|
|
|
757
841
|
let attempt = 0;
|
|
842
|
+
|
|
758
843
|
for (const plugin of fallbackPlugins) {
|
|
759
844
|
attempt++;
|
|
845
|
+
|
|
760
846
|
if (attempt > (this.options.maxFallbackAttempts ?? 3)) {
|
|
761
|
-
this.debug(`[Stream] Max attempts reached`);
|
|
847
|
+
this.debug(`[Stream] Max fallback attempts reached`);
|
|
762
848
|
break;
|
|
763
849
|
}
|
|
764
850
|
|
|
765
|
-
|
|
766
|
-
this.debug(`[Stream] Fallback ${attempt}: ${plugin.name}`);
|
|
767
|
-
const controller = new AbortController();
|
|
768
|
-
|
|
769
|
-
let result: StreamInfo | null = null;
|
|
851
|
+
const { result, similarity } = await tryPlugin(plugin);
|
|
770
852
|
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
853
|
+
if (!result?.stream) {
|
|
854
|
+
continue;
|
|
855
|
+
}
|
|
774
856
|
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
}
|
|
857
|
+
// Perfect / good match
|
|
858
|
+
if (similarity >= 0.7) {
|
|
859
|
+
this.debug(`[Stream] Success via fallback ${plugin.name} (score: ${similarity})`);
|
|
778
860
|
|
|
779
|
-
|
|
780
|
-
const similarityScore = this.calculateTrackSimilarity(track, result);
|
|
861
|
+
this.setCachedStream(track, result);
|
|
781
862
|
|
|
782
|
-
|
|
783
|
-
this.debug(`[Stream] Fallback success via ${plugin.name} (score: ${similarityScore})`);
|
|
784
|
-
this.setCachedStream(track, result);
|
|
785
|
-
return result;
|
|
786
|
-
} else {
|
|
787
|
-
validResults.push({
|
|
788
|
-
plugin: plugin.name,
|
|
789
|
-
streamInfo: result,
|
|
790
|
-
score: similarityScore,
|
|
791
|
-
});
|
|
792
|
-
this.debug(`[Stream] Fallback ${plugin.name} returned low similarity (${similarityScore})`);
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
} catch (error) {
|
|
796
|
-
this.debug(`[Stream] Fallback failed: ${plugin.name}`, error);
|
|
863
|
+
return result;
|
|
797
864
|
}
|
|
865
|
+
|
|
866
|
+
// Keep low similarity result as backup
|
|
867
|
+
validResults.push({
|
|
868
|
+
plugin: plugin.name,
|
|
869
|
+
streamInfo: result,
|
|
870
|
+
score: similarity,
|
|
871
|
+
});
|
|
872
|
+
|
|
873
|
+
this.debug(`[Stream] ${plugin.name} low similarity match (${similarity})`);
|
|
798
874
|
}
|
|
799
875
|
|
|
876
|
+
// =========================================================
|
|
877
|
+
// BEST AVAILABLE MATCH
|
|
878
|
+
// =========================================================
|
|
800
879
|
if (validResults.length > 0) {
|
|
801
880
|
const bestMatch = validResults.sort((a, b) => b.score - a.score)[0];
|
|
802
|
-
|
|
881
|
+
|
|
882
|
+
this.debug(`[Stream] Using best available match from ${bestMatch.plugin} (${bestMatch.score})`);
|
|
883
|
+
|
|
884
|
+
this.setCachedStream(track, bestMatch.streamInfo);
|
|
885
|
+
|
|
803
886
|
return bestMatch.streamInfo;
|
|
804
887
|
}
|
|
805
888
|
|
|
806
|
-
this.debug(`[Stream] All plugins failed for
|
|
889
|
+
this.debug(`[Stream] All plugins failed for: ${track.title}`);
|
|
890
|
+
|
|
807
891
|
return null;
|
|
808
892
|
}
|
|
809
|
-
|
|
810
893
|
async getStream(track: Track): Promise<StreamInfo | null> {
|
|
811
894
|
if (!track) {
|
|
812
895
|
this.debug(`[getStream] No track provided`);
|