@lucaperret/tidal-cli 1.1.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.
- package/LICENSE +21 -0
- package/README.md +204 -0
- package/dist/album.d.ts +2 -0
- package/dist/album.js +124 -0
- package/dist/artist.d.ts +5 -0
- package/dist/artist.js +230 -0
- package/dist/auth.d.ts +5 -0
- package/dist/auth.js +226 -0
- package/dist/history.d.ts +3 -0
- package/dist/history.js +69 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +372 -0
- package/dist/library.d.ts +7 -0
- package/dist/library.js +131 -0
- package/dist/playback.d.ts +3 -0
- package/dist/playback.js +284 -0
- package/dist/playlist.d.ts +9 -0
- package/dist/playlist.js +269 -0
- package/dist/recommend.d.ts +1 -0
- package/dist/recommend.js +36 -0
- package/dist/search.d.ts +4 -0
- package/dist/search.js +183 -0
- package/dist/session.d.ts +7 -0
- package/dist/session.js +120 -0
- package/dist/track.d.ts +4 -0
- package/dist/track.js +235 -0
- package/dist/user.d.ts +1 -0
- package/dist/user.js +36 -0
- package/package.json +59 -0
package/dist/library.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.addToLibrary = addToLibrary;
|
|
4
|
+
exports.removeFromLibrary = removeFromLibrary;
|
|
5
|
+
exports.listFavoritedPlaylists = listFavoritedPlaylists;
|
|
6
|
+
exports.addPlaylistToFavorites = addPlaylistToFavorites;
|
|
7
|
+
exports.removePlaylistFromFavorites = removePlaylistFromFavorites;
|
|
8
|
+
const auth_1 = require("./auth");
|
|
9
|
+
const collectionEndpoints = {
|
|
10
|
+
artist: { path: '/userCollectionArtists/{id}/relationships/items', type: 'artists' },
|
|
11
|
+
album: { path: '/userCollectionAlbums/{id}/relationships/items', type: 'albums' },
|
|
12
|
+
track: { path: '/userCollectionTracks/{id}/relationships/items', type: 'tracks' },
|
|
13
|
+
video: { path: '/userCollectionVideos/{id}/relationships/items', type: 'videos' },
|
|
14
|
+
};
|
|
15
|
+
async function addToLibrary(resourceType, resourceId, json) {
|
|
16
|
+
const client = await (0, auth_1.getApiClient)();
|
|
17
|
+
const endpoint = collectionEndpoints[resourceType];
|
|
18
|
+
const { error } = await client.POST(endpoint.path, {
|
|
19
|
+
params: { path: { id: 'me' } },
|
|
20
|
+
body: {
|
|
21
|
+
data: [{ id: resourceId, type: endpoint.type }],
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
if (error) {
|
|
25
|
+
console.error(`Error: Failed to add ${resourceType} to library — ${JSON.stringify(error)}`);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
if (json) {
|
|
29
|
+
console.log(JSON.stringify({ resourceType, resourceId, added: true }, null, 2));
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
console.log(`\n${capitalize(resourceType)} ${resourceId} added to your library.`);
|
|
33
|
+
}
|
|
34
|
+
async function removeFromLibrary(resourceType, resourceId, json) {
|
|
35
|
+
const client = await (0, auth_1.getApiClient)();
|
|
36
|
+
const endpoint = collectionEndpoints[resourceType];
|
|
37
|
+
const { error } = await client.DELETE(endpoint.path, {
|
|
38
|
+
params: { path: { id: 'me' } },
|
|
39
|
+
body: {
|
|
40
|
+
data: [{ id: resourceId, type: endpoint.type }],
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
if (error) {
|
|
44
|
+
console.error(`Error: Failed to remove ${resourceType} from library — ${JSON.stringify(error)}`);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
if (json) {
|
|
48
|
+
console.log(JSON.stringify({ resourceType, resourceId, removed: true }, null, 2));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
console.log(`\n${capitalize(resourceType)} ${resourceId} removed from your library.`);
|
|
52
|
+
}
|
|
53
|
+
async function listFavoritedPlaylists(json) {
|
|
54
|
+
const client = await (0, auth_1.getApiClient)();
|
|
55
|
+
const { data, error } = await client.GET('/userCollectionPlaylists/{id}/relationships/items', {
|
|
56
|
+
params: {
|
|
57
|
+
path: { id: 'me' },
|
|
58
|
+
query: {
|
|
59
|
+
include: ['items'],
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
if (error || !data) {
|
|
64
|
+
console.error(`Error: Failed to list favorited playlists — ${JSON.stringify(error)}`);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
const included = data.included ?? [];
|
|
68
|
+
const playlists = included
|
|
69
|
+
.filter((item) => item.type === 'playlists')
|
|
70
|
+
.map((item) => {
|
|
71
|
+
const attrs = item.attributes;
|
|
72
|
+
return {
|
|
73
|
+
id: item.id,
|
|
74
|
+
name: attrs?.name ?? 'Unknown',
|
|
75
|
+
numberOfItems: attrs?.numberOfItems,
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
if (json) {
|
|
79
|
+
console.log(JSON.stringify(playlists, null, 2));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (playlists.length === 0) {
|
|
83
|
+
console.log('No favorited playlists found.');
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
console.log('\nFavorited playlists:\n');
|
|
87
|
+
for (const p of playlists) {
|
|
88
|
+
console.log(` [${p.id}] ${p.name} (${p.numberOfItems ?? 0} items)`);
|
|
89
|
+
}
|
|
90
|
+
console.log();
|
|
91
|
+
}
|
|
92
|
+
async function addPlaylistToFavorites(playlistId, json) {
|
|
93
|
+
const client = await (0, auth_1.getApiClient)();
|
|
94
|
+
const { error } = await client.POST('/userCollectionPlaylists/{id}/relationships/items', {
|
|
95
|
+
params: { path: { id: 'me' } },
|
|
96
|
+
body: {
|
|
97
|
+
data: [{ id: playlistId, type: 'playlists' }],
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
if (error) {
|
|
101
|
+
console.error(`Error: Failed to add playlist to favorites — ${JSON.stringify(error)}`);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
if (json) {
|
|
105
|
+
console.log(JSON.stringify({ playlistId, added: true }, null, 2));
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
console.log(`\nPlaylist ${playlistId} added to favorites.`);
|
|
109
|
+
}
|
|
110
|
+
async function removePlaylistFromFavorites(playlistId, json) {
|
|
111
|
+
const client = await (0, auth_1.getApiClient)();
|
|
112
|
+
const { error } = await client.DELETE('/userCollectionPlaylists/{id}/relationships/items', {
|
|
113
|
+
params: { path: { id: 'me' } },
|
|
114
|
+
body: {
|
|
115
|
+
data: [{ id: playlistId, type: 'playlists' }],
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
if (error) {
|
|
119
|
+
console.error(`Error: Failed to remove playlist from favorites — ${JSON.stringify(error)}`);
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
if (json) {
|
|
123
|
+
console.log(JSON.stringify({ playlistId, removed: true }, null, 2));
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
console.log(`\nPlaylist ${playlistId} removed from favorites.`);
|
|
127
|
+
}
|
|
128
|
+
function capitalize(s) {
|
|
129
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=library.js.map
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare function playbackInfo(trackId: string, quality: string, json: boolean): Promise<void>;
|
|
2
|
+
export declare function playbackUrl(trackId: string, quality: string, json: boolean): Promise<void>;
|
|
3
|
+
export declare function playbackPlay(trackId: string, quality: string): Promise<void>;
|
package/dist/playback.js
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.playbackInfo = playbackInfo;
|
|
37
|
+
exports.playbackUrl = playbackUrl;
|
|
38
|
+
exports.playbackPlay = playbackPlay;
|
|
39
|
+
const auth_1 = require("./auth");
|
|
40
|
+
const child_process_1 = require("child_process");
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
const os = __importStar(require("os"));
|
|
44
|
+
const qualityToFormats = {
|
|
45
|
+
LOW: ['HEAACV1'],
|
|
46
|
+
HIGH: ['HEAACV1', 'AACLC'],
|
|
47
|
+
LOSSLESS: ['HEAACV1', 'AACLC', 'FLAC'],
|
|
48
|
+
HI_RES: ['HEAACV1', 'AACLC', 'FLAC', 'FLAC_HIRES'],
|
|
49
|
+
};
|
|
50
|
+
function formatsToQuality(formats) {
|
|
51
|
+
if (formats.includes('FLAC_HIRES'))
|
|
52
|
+
return 'HI_RES_LOSSLESS';
|
|
53
|
+
if (formats.includes('FLAC'))
|
|
54
|
+
return 'LOSSLESS';
|
|
55
|
+
if (formats.includes('AACLC'))
|
|
56
|
+
return 'HIGH';
|
|
57
|
+
return 'LOW';
|
|
58
|
+
}
|
|
59
|
+
function parseDataUri(dataUri) {
|
|
60
|
+
const match = dataUri.match(/^data:([^;]+);base64,(.+)$/);
|
|
61
|
+
if (!match)
|
|
62
|
+
throw new Error('Invalid data URI from trackManifests');
|
|
63
|
+
return { mimeType: match[1], data: match[2] };
|
|
64
|
+
}
|
|
65
|
+
async function fetchTrackManifest(trackId, quality) {
|
|
66
|
+
const client = await (0, auth_1.getApiClient)();
|
|
67
|
+
const formats = qualityToFormats[quality] ?? qualityToFormats.HIGH;
|
|
68
|
+
const { data, error } = await client.GET('/trackManifests/{id}', {
|
|
69
|
+
params: {
|
|
70
|
+
path: { id: trackId },
|
|
71
|
+
query: {
|
|
72
|
+
adaptive: false,
|
|
73
|
+
formats: formats,
|
|
74
|
+
manifestType: 'MPEG_DASH',
|
|
75
|
+
uriScheme: 'DATA',
|
|
76
|
+
usage: 'PLAYBACK',
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
if (error || !data) {
|
|
81
|
+
console.error(`Error: Failed to get track manifest — ${JSON.stringify(error)}`);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
const attrs = data.data?.attributes;
|
|
85
|
+
if (!attrs?.uri) {
|
|
86
|
+
console.error('Error: No manifest URI in response.');
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
const { mimeType, data: manifestBase64 } = parseDataUri(attrs.uri);
|
|
90
|
+
return {
|
|
91
|
+
trackId: data.data?.id ?? trackId,
|
|
92
|
+
trackPresentation: attrs.trackPresentation ?? 'UNKNOWN',
|
|
93
|
+
previewReason: attrs.previewReason,
|
|
94
|
+
formats: attrs.formats ?? [],
|
|
95
|
+
audioQuality: formatsToQuality(attrs.formats ?? []),
|
|
96
|
+
manifestMimeType: mimeType,
|
|
97
|
+
manifest: manifestBase64,
|
|
98
|
+
trackReplayGain: attrs.trackAudioNormalizationData?.replayGain ?? 0,
|
|
99
|
+
trackPeakAmplitude: attrs.trackAudioNormalizationData?.peakAmplitude ?? 0,
|
|
100
|
+
albumReplayGain: attrs.albumAudioNormalizationData?.replayGain ?? 0,
|
|
101
|
+
albumPeakAmplitude: attrs.albumAudioNormalizationData?.peakAmplitude ?? 0,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function decodeManifest(base64Manifest, mimeType) {
|
|
105
|
+
const decoded = Buffer.from(base64Manifest, 'base64').toString('utf-8');
|
|
106
|
+
// JSON manifest (BTS): has urls array
|
|
107
|
+
if (mimeType.includes('tidal.bts') || mimeType.includes('json')) {
|
|
108
|
+
try {
|
|
109
|
+
const json = JSON.parse(decoded);
|
|
110
|
+
if (json.urls?.length > 0) {
|
|
111
|
+
return { type: 'direct', url: json.urls[0], codecs: json.codecs };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch { }
|
|
115
|
+
}
|
|
116
|
+
// DASH XML manifest
|
|
117
|
+
if (mimeType.includes('dash') || decoded.includes('<MPD')) {
|
|
118
|
+
const initMatch = decoded.match(/initialization="([^"]+)"/);
|
|
119
|
+
const mediaMatch = decoded.match(/media="([^"]+)"/);
|
|
120
|
+
const codecsMatch = decoded.match(/codecs="([^"]+)"/);
|
|
121
|
+
// Parse segment timeline: <S d="176128" r="6"/> means 7 segments, <S d="89088"/> means 1
|
|
122
|
+
const segmentDurations = [];
|
|
123
|
+
const sMatches = decoded.matchAll(/<S d="(\d+)"(?:\s+r="(\d+)")?\/>/g);
|
|
124
|
+
let segNum = 1;
|
|
125
|
+
for (const m of sMatches) {
|
|
126
|
+
const repeat = m[2] ? parseInt(m[2]) + 1 : 1;
|
|
127
|
+
for (let i = 0; i < repeat; i++) {
|
|
128
|
+
segmentDurations.push(segNum++);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (initMatch && mediaMatch && segmentDurations.length > 0) {
|
|
132
|
+
return {
|
|
133
|
+
type: 'dash',
|
|
134
|
+
dash: {
|
|
135
|
+
initUrl: initMatch[1],
|
|
136
|
+
mediaTemplate: mediaMatch[1],
|
|
137
|
+
segments: segmentDurations,
|
|
138
|
+
},
|
|
139
|
+
codecs: codecsMatch?.[1],
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
// Fallback: try BaseURL
|
|
143
|
+
const baseUrlMatch = decoded.match(/<BaseURL>([^<]+)<\/BaseURL>/);
|
|
144
|
+
if (baseUrlMatch) {
|
|
145
|
+
return { type: 'direct', url: baseUrlMatch[1], codecs: codecsMatch?.[1] };
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
throw new Error('Unable to parse manifest');
|
|
149
|
+
}
|
|
150
|
+
async function downloadDashStream(dash) {
|
|
151
|
+
// Download init segment
|
|
152
|
+
const initRes = await fetch(dash.initUrl);
|
|
153
|
+
if (!initRes.ok)
|
|
154
|
+
throw new Error(`Failed to download init segment (${initRes.status})`);
|
|
155
|
+
const initBuf = Buffer.from(await initRes.arrayBuffer());
|
|
156
|
+
// Download media segments
|
|
157
|
+
const segBuffers = [initBuf];
|
|
158
|
+
for (const segNum of dash.segments) {
|
|
159
|
+
const segUrl = dash.mediaTemplate.replace('$Number$', String(segNum));
|
|
160
|
+
const segRes = await fetch(segUrl);
|
|
161
|
+
if (!segRes.ok)
|
|
162
|
+
throw new Error(`Failed to download segment ${segNum} (${segRes.status})`);
|
|
163
|
+
segBuffers.push(Buffer.from(await segRes.arrayBuffer()));
|
|
164
|
+
}
|
|
165
|
+
return Buffer.concat(segBuffers);
|
|
166
|
+
}
|
|
167
|
+
async function playbackInfo(trackId, quality, json) {
|
|
168
|
+
const info = await fetchTrackManifest(trackId, quality);
|
|
169
|
+
const result = {
|
|
170
|
+
trackId: info.trackId,
|
|
171
|
+
presentation: info.trackPresentation,
|
|
172
|
+
previewReason: info.previewReason,
|
|
173
|
+
audioQuality: info.audioQuality,
|
|
174
|
+
formats: info.formats,
|
|
175
|
+
manifestMimeType: info.manifestMimeType,
|
|
176
|
+
trackReplayGain: info.trackReplayGain,
|
|
177
|
+
trackPeakAmplitude: info.trackPeakAmplitude,
|
|
178
|
+
albumReplayGain: info.albumReplayGain,
|
|
179
|
+
albumPeakAmplitude: info.albumPeakAmplitude,
|
|
180
|
+
};
|
|
181
|
+
if (json) {
|
|
182
|
+
console.log(JSON.stringify(result, null, 2));
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
console.log(`\nPlayback info for track ${trackId}:\n`);
|
|
186
|
+
console.log(` Quality: ${result.audioQuality}`);
|
|
187
|
+
console.log(` Formats: ${result.formats.join(', ')}`);
|
|
188
|
+
console.log(` Presentation: ${result.presentation}`);
|
|
189
|
+
if (result.previewReason) {
|
|
190
|
+
console.log(` Preview reason: ${result.previewReason}`);
|
|
191
|
+
}
|
|
192
|
+
console.log(` Manifest type: ${result.manifestMimeType}`);
|
|
193
|
+
console.log(` Track gain: ${result.trackReplayGain} dB`);
|
|
194
|
+
console.log(` Album gain: ${result.albumReplayGain} dB`);
|
|
195
|
+
console.log();
|
|
196
|
+
}
|
|
197
|
+
async function playbackUrl(trackId, quality, json) {
|
|
198
|
+
const info = await fetchTrackManifest(trackId, quality);
|
|
199
|
+
const stream = decodeManifest(info.manifest, info.manifestMimeType);
|
|
200
|
+
if (stream.type === 'direct') {
|
|
201
|
+
if (json) {
|
|
202
|
+
console.log(JSON.stringify({ trackId: info.trackId, url: stream.url, audioQuality: info.audioQuality }, null, 2));
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
console.log(stream.url);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
else if (stream.dash) {
|
|
209
|
+
if (json) {
|
|
210
|
+
console.log(JSON.stringify({
|
|
211
|
+
trackId: info.trackId,
|
|
212
|
+
type: 'dash',
|
|
213
|
+
initUrl: stream.dash.initUrl,
|
|
214
|
+
segmentCount: stream.dash.segments.length,
|
|
215
|
+
audioQuality: info.audioQuality,
|
|
216
|
+
}, null, 2));
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
console.log(`DASH stream (${stream.dash.segments.length} segments)`);
|
|
220
|
+
console.log(` Init: ${stream.dash.initUrl}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
console.error('Error: No stream URL available for this track.');
|
|
225
|
+
process.exit(1);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
async function playbackPlay(trackId, quality) {
|
|
229
|
+
const info = await fetchTrackManifest(trackId, quality);
|
|
230
|
+
const stream = decodeManifest(info.manifest, info.manifestMimeType);
|
|
231
|
+
// Determine file extension from format
|
|
232
|
+
const isFlac = info.formats.includes('FLAC') || info.formats.includes('FLAC_HIRES');
|
|
233
|
+
const ext = isFlac ? '.flac' : '.mp4';
|
|
234
|
+
const tmpFile = path.join(os.tmpdir(), `tidal-${trackId}${ext}`);
|
|
235
|
+
console.log(`\nDownloading track ${trackId} (${info.audioQuality}, ${info.formats.join('/')})...`);
|
|
236
|
+
let audioData;
|
|
237
|
+
if (stream.type === 'direct' && stream.url) {
|
|
238
|
+
const streamRes = await fetch(stream.url);
|
|
239
|
+
if (!streamRes.ok) {
|
|
240
|
+
console.error(`Error: Failed to download stream (${streamRes.status}).`);
|
|
241
|
+
process.exit(1);
|
|
242
|
+
}
|
|
243
|
+
audioData = Buffer.from(await streamRes.arrayBuffer());
|
|
244
|
+
}
|
|
245
|
+
else if (stream.type === 'dash' && stream.dash) {
|
|
246
|
+
audioData = await downloadDashStream(stream.dash);
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
console.error('Error: No stream available for this track.');
|
|
250
|
+
process.exit(1);
|
|
251
|
+
}
|
|
252
|
+
fs.writeFileSync(tmpFile, audioData);
|
|
253
|
+
const player = process.platform === 'darwin' ? 'afplay'
|
|
254
|
+
: process.platform === 'win32' ? 'start'
|
|
255
|
+
: 'mpv --no-video';
|
|
256
|
+
console.log(`Playing... Press Ctrl+C to stop.\n`);
|
|
257
|
+
return new Promise((resolve, reject) => {
|
|
258
|
+
const child = (0, child_process_1.exec)(`${player} "${tmpFile}"`, (err) => {
|
|
259
|
+
try {
|
|
260
|
+
fs.unlinkSync(tmpFile);
|
|
261
|
+
}
|
|
262
|
+
catch { }
|
|
263
|
+
if (err && err.killed) {
|
|
264
|
+
resolve();
|
|
265
|
+
}
|
|
266
|
+
else if (err) {
|
|
267
|
+
reject(new Error(`Playback failed: ${err.message}`));
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
resolve();
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
process.on('SIGINT', () => {
|
|
274
|
+
child.kill();
|
|
275
|
+
try {
|
|
276
|
+
fs.unlinkSync(tmpFile);
|
|
277
|
+
}
|
|
278
|
+
catch { }
|
|
279
|
+
console.log('\nPlayback stopped.');
|
|
280
|
+
process.exit(0);
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
//# sourceMappingURL=playback.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function listPlaylists(json: boolean): Promise<void>;
|
|
2
|
+
export declare function createPlaylist(name: string, description: string, json: boolean): Promise<void>;
|
|
3
|
+
export declare function renamePlaylist(playlistId: string, name: string, json: boolean): Promise<void>;
|
|
4
|
+
export declare function deletePlaylist(playlistId: string, json: boolean): Promise<void>;
|
|
5
|
+
export declare function addTrackToPlaylist(playlistId: string, trackId: string, json: boolean): Promise<void>;
|
|
6
|
+
export declare function removeTrackFromPlaylist(playlistId: string, trackId: string, json: boolean): Promise<void>;
|
|
7
|
+
export declare function addAlbumToPlaylist(playlistId: string, albumId: string, json: boolean): Promise<void>;
|
|
8
|
+
export declare function moveTrackInPlaylist(playlistId: string, trackId: string, positionBefore: string, json: boolean): Promise<void>;
|
|
9
|
+
export declare function updatePlaylistDescription(playlistId: string, description: string, json: boolean): Promise<void>;
|