@khang07/zing-mp3-api 1.3.6 → 1.4.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/README.md CHANGED
@@ -1,328 +1,379 @@
1
- # `@khang07/zing-mp3-api`
2
-
3
- The basic APIs provide the features of ZingMp3.
4
-
5
- ## Features
6
-
7
- * Search songs, videos, playlists, and artists
8
- * Fetch playlist, artist, and song details
9
- * Get readable streams for music and video
10
- * Accept raw resource tokens or `URL` values for resource-based methods
11
- * Provide a cookie jar utility through a public subpath export
12
- * Ship ESM and CommonJS entry points
13
-
14
- ## Installation
15
-
16
- ```bash
17
- npm install @khang07/zing-mp3-api
18
- ```
19
-
20
- ## Public Exports
21
-
22
- ### Root export
23
-
24
- #### Runtime exports
25
-
26
- * `default`: a pre-created `Client` instance
27
- * `Client`: the client class
28
-
29
- #### Type exports
30
-
31
- * `ClientOptions`
32
- * `PlayList`
33
- * `Artist`
34
- * `Media`
35
- * `SearhMedia`
36
- * `SearchPlayList`
37
- * `Cookies` *(type-only export from the root entry)*
38
-
39
- ### Public subpath exports
40
-
41
- ```ts
42
- import { Cookies } from "@khang07/zing-mp3-api/utils/cookies";
43
- import { createSignature } from "@khang07/zing-mp3-api/utils/encrypt";
44
- import { Lapse } from "@khang07/zing-mp3-api/utils/lapse";
45
- ```
46
-
47
- ## Import
48
-
49
- ### ESM
50
-
51
- ```ts
52
- import client, { Client } from "@khang07/zing-mp3-api";
53
- ```
54
-
55
- ### CommonJS
56
-
57
- ```js
58
- const zing = require("@khang07/zing-mp3-api");
59
-
60
- const client = zing.default;
61
- const { Client } = zing;
62
- ```
63
-
64
- ## Basic Usage
65
-
66
- ### Use the default client
67
-
68
- ```ts
69
- import client from "@khang07/zing-mp3-api";
70
-
71
- const items = await client.searchMusic("Do For Love Bray");
72
- console.log(items[0]);
73
- ```
74
-
75
- ### Create a client
76
-
77
- ```ts
78
- import { Client } from "@khang07/zing-mp3-api";
79
- import { Cookies } from "@khang07/zing-mp3-api/utils/cookies";
80
-
81
- const client = new Client({
82
- maxLoad: 16 * 1024,
83
- maxHighWaterMark: 16 * 1024,
84
- userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
85
- jar: new Cookies()
86
- });
87
- ```
88
-
89
- ### Fetch a playlist from a URL
90
-
91
- ```ts
92
- import client from "@khang07/zing-mp3-api";
93
-
94
- const playlist = await client.playlist("https://zingmp3.vn/album/example/ZWZB9WAB.html");
95
- console.log(playlist.name);
96
- console.log(playlist.mediaCount);
97
- ```
98
-
99
- ### Fetch artist details
100
-
101
- ```ts
102
- import client from "@khang07/zing-mp3-api";
103
-
104
- const artist = await client.artist("https://zingmp3.vn/Obito");
105
- console.log(artist.name);
106
- console.log(artist.followCount);
107
- ```
108
-
109
- ### Download a music stream
110
-
111
- ```ts
112
- import { createWriteStream } from "node:fs";
113
- import client from "@khang07/zing-mp3-api";
114
-
115
- const items = await client.searchMusic("Do For Love Bray");
116
- const stream = await client.music(items[0].id);
117
-
118
- stream.pipe(createWriteStream("music.mp3"));
119
- ```
120
-
121
- ### Download a video stream
122
-
123
- ```ts
124
- import { createWriteStream } from "node:fs";
125
- import client from "@khang07/zing-mp3-api";
126
-
127
- const items = await client.searchVideo("Do For Love Bray");
128
- const stream = await client.video(items[0].id);
129
-
130
- stream.pipe(createWriteStream("video.ts"));
131
- ```
132
-
133
- ### Use the immediate stream-returning methods
134
-
135
- ```ts
136
- import { createWriteStream } from "node:fs";
137
- import client from "@khang07/zing-mp3-api";
138
-
139
- const stream = client.musicSync("ZWZB9WAB");
140
- stream.pipe(createWriteStream("music.mp3"));
141
- ```
142
-
143
- ### Handle library errors
144
-
145
- ```ts
146
- import client from "@khang07/zing-mp3-api";
147
- import { Lapse } from "@khang07/zing-mp3-api/utils/lapse";
148
-
149
- try {
150
- await client.playlist("");
151
- } catch (error) {
152
- if (error instanceof Lapse) {
153
- console.error(error.name);
154
- console.error(error.code);
155
- console.error(error.status);
156
- }
157
- }
158
- ```
159
-
160
- ## API Reference
161
-
162
- ## `new Client(options?)`
163
-
164
- Creates a new client instance.
165
-
166
- ### `ClientOptions`
167
-
168
- | Field | Type | Default |
169
- | ------------------ | --------- | -------------------------------------------------------------------------------------------------------------------- |
170
- | `maxLoad` | `number` | `1024 * 1024` |
171
- | `maxHighWaterMark` | `number` | `16 * 1024` |
172
- | `userAgent` | `string` | `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3` |
173
- | `jar` | `Cookies` | `new Cookies()` |
174
-
175
- `maxLoad` is passed to Axios as `maxRate`.
176
-
177
- `maxHighWaterMark` is used as the `highWaterMark` when `musicSync()` and `videoSync()` create a `PassThrough` stream.
178
-
179
- ## `Client.getIDFromURL(url)`
180
-
181
- ```ts
182
- static getIDFromURL(url: string): string
183
- ```
184
-
185
- Extracts a resource token from a ZingMp3 URL.
186
-
187
- It throws `ERROR_INVALID_URL` when the input is not a non-empty string or when no token can be extracted.
188
-
189
- ## Instance Methods
190
-
191
- | Method | Returns | Description |
192
- | ----------------------- | ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
193
- | `video(videoID)` | `Promise<Readable>` | Fetches a video stream. Accepts a raw token, URL string, or `URL`. |
194
- | `videoSync(videoID)` | `Readable` | Returns a `PassThrough` immediately and pipes the fetched video stream into it. |
195
- | `music(musicID)` | `Promise<Readable>` | Fetches a music stream. Accepts a raw token, URL string, or `URL`. |
196
- | `musicSync(musicID)` | `Readable` | Returns a `PassThrough` immediately and pipes the fetched music stream into it. |
197
- | `playlist(playlistID)` | `Promise<PlayList>` | Fetches playlist details. Accepts a raw token, URL string, or `URL`. |
198
- | `artist(aliasID)` | `Promise<Artist>` | Fetches artist details. Accepts an alias token, URL string, or `URL`. |
199
- | `mediaDetails(mediaID)` | `Promise<Media>` | Fetches song details. Accepts a raw token, URL string, or `URL`. |
200
- | `searchMusic(query)` | `Promise<SearhMedia[]>` | Searches songs. |
201
- | `searchVideo(query)` | `Promise<SearhMedia[]>` | Searches videos. |
202
- | `searchList(query)` | `Promise<SearchPlayList[]>` | Searches playlists. |
203
- | `searchArtist(query)` | `Promise<SearchArtist[]>` | Searches artists. |
204
-
205
- ### Method notes
206
-
207
- * `video()` selects `720p` first, then falls back to `360p`.
208
- * `music()` selects `320` first when available and not equal to `"VIP"`, then falls back to `128`.
209
- * When the music API returns `err === -1150`, `music()` retries the extra music endpoint up to 4 times and throws `ERROR_MUSIC_VIP_ONLY` if it still cannot resolve a playable URL.
210
- * `searchMusic()`, `searchVideo()`, `searchList()`, and `searchArtist()` always request `page: 1` and `count: 20`.
211
-
212
- ## Public Types:
213
- - ### `Artist`
214
- - ### `PlayList`
215
- - ### `Media`
216
- - ### `SearchMedia`
217
- - ### `SearchPlayList`
218
- - ### `SearchArtist`
219
- - ### `Cookies`
220
-
221
- ### Methods
222
-
223
- | Method | Returns |
224
- | -------------------------------------- | ------------------------ |
225
- | `setCookie(setCookie, requestUrl)` | `void` |
226
- | `setCookies(setCookies, requestUrl)` | `void` |
227
- | `getCookies(requestUrl)` | `CookieRecord[]` |
228
- | `getCookieHeader(requestUrl)` | `string` |
229
- | `applyToHeaders(requestUrl, headers?)` | `Record<string, string>` |
230
- | `deleteCookie(domain, path, name)` | `void` |
231
- | `cleanup()` | `void` |
232
- | `toJSON()` | `CookieRecord[]` |
233
- | `fromJSON(cookies)` | `void` |
234
-
235
- ### Cookie record shape
236
-
237
- ```ts
238
- interface CookieRecord {
239
- name: string;
240
- value: string;
241
- domain: string;
242
- path: string;
243
- expiresAt?: number;
244
- secure: boolean;
245
- httpOnly: boolean;
246
- sameSite?: "Strict" | "Lax" | "None";
247
- hostOnly: boolean;
248
- }
249
- ```
250
-
251
- ### Example
252
-
253
- ```ts
254
- import { Cookies } from "@khang07/zing-mp3-api/utils/cookies";
255
-
256
- const jar = new Cookies();
257
- jar.setCookie("sid=abc; Path=/; HttpOnly", "https://zingmp3.vn/");
258
-
259
- console.log(jar.getCookieHeader("https://zingmp3.vn/"));
260
- ```
261
-
262
- ## `createSignature(uri, params, secret)`
263
-
264
- Import from `@khang07/zing-mp3-api/utils/encrypt`.
265
-
266
- ```ts
267
- function createSignature(uri: string, params: string, secret: string): string
268
- ```
269
-
270
- Generates the request signature used by the client.
271
-
272
- ## `Lapse`
273
-
274
- Import from `@khang07/zing-mp3-api/utils/lapse`.
275
-
276
- ```ts
277
- class Lapse extends Error {
278
- code: string;
279
- status?: number;
280
- cause?: unknown;
281
- }
282
- ```
283
-
284
- ### Constructor
285
-
286
- ```ts
287
- new Lapse(message: string, code: string, status?: number, cause?: unknown)
288
- ```
289
-
290
- ### Properties
291
-
292
- * `name`: always set to `"ZING_MP3_ERROR"`
293
- * `message`: inherited from `Error`
294
- * `code`: library error code
295
- * `status`: optional status or API error value
296
- * `cause`: optional original error or response payload
297
-
298
- ## Error Codes
299
-
300
- | Code | Used by |
301
- | ---------------------------- | ------------------------------------------------------------------ |
302
- | `ERROR_INVALID_URL` | `Client.getIDFromURL()` |
303
- | `ERROR_INVALID_ID` | `video()`, `music()`, `playlist()`, `artist()`, `mediaDetails()` |
304
- | `ERROR_INVALID_QUERY` | `searchMusic()`, `searchVideo()`, `searchList()`, `searchArtist()` |
305
- | `ERROR_VIDEO_NOT_FOUND` | `video()` |
306
- | `ERROR_VIDEO_FETCH` | `video()`, `videoSync()` |
307
- | `ERROR_MUSIC_VIP_ONLY` | `music()` |
308
- | `ERROR_MUSIC_FETCH` | `music()`, `musicSync()` |
309
- | `ERROR_PLAYLIST_NOT_FOUND` | `playlist()` |
310
- | `ERROR_PLAYLIST_FETCH` | `playlist()` |
311
- | `ERROR_ARTIST_NOT_FOUND` | `artist()` |
312
- | `ERROR_ARTIST_FETCH` | `artist()` |
313
- | `ERROR_MEDIA_NOT_FOUND` | `mediaDetails()` |
314
- | `ERROR_MEDIA_FETCH` | `mediaDetails()` |
315
- | `ERROR_SEARCH_FAILED` | `searchMusic()`, `searchVideo()`, `searchList()`, `searchArtist()` |
316
- | `ERROR_SEARCH_FETCH` | `searchMusic()`, `searchVideo()`, `searchList()`, `searchArtist()` |
317
- | `ERROR_STREAM_URL_NOT_FOUND` | `video()`, `music()` |
318
- | `ERROR_STREAM_DOWNLOAD` | `video()`, `videoSync()`, `music()`, `musicSync()` |
319
-
320
- ## Notes
321
-
322
- * The root entry exports `Cookies` as a type only. To construct a cookie jar, import `Cookies` from `@khang07/zing-mp3-api/utils/cookies`.
323
- * The root entry exports the type name `SearhMedia` exactly as written in source.
324
- * `searchArtist()` is a public method, but its `SearchArtist` type is not re-exported from the root entry.
325
- * No test, example, or demo files were included in the provided source bundle.
326
-
327
- ## License
328
- [MIT](https://github.com/GiaKhang1810/zing-mp3-api?tab=MIT-1-ov-file)
1
+ # **@KHANG07/ZING-MP3-API**
2
+
3
+ A TypeScript library for working with [Zing MP3](https://zingmp3.vn) resources from Node.js.
4
+ It can search songs, videos, playlists, and artists; fetch detailed metadata;
5
+ and return readable streams for music and video playback or download.
6
+
7
+ For the source code, releases, and package metadata, visit the
8
+ [GitHub repository](https://github.com/GiaKhang1810/zing-mp3-api) and the
9
+ [npm package](https://www.npmjs.com/package/@khang07/zing-mp3-api).
10
+ Report bugs or request features in the
11
+ [issue tracker](https://github.com/GiaKhang1810/zing-mp3-api/issues).
12
+
13
+
14
+ ## Table of contents
15
+
16
+ - [Requirement](#requirements)
17
+ - [Installation](#installation)
18
+ - [Configuration](#configuration)
19
+ - [Usage](#usage)
20
+ - [API reference](#api-reference)
21
+ - [Public exports](#public-exports)
22
+ - [Error handling](#error-handling)
23
+ - [Troubleshooting & FAQ](#troubleshooting--faq)
24
+ - [Maintainers](#maintainers)
25
+ - [License](#license)
26
+
27
+
28
+ ## Requirements
29
+
30
+ This package has no special runtime requirements beyond a modern Node.js
31
+ environment and network access to [Zing MP3](https://zingmp3.vn).
32
+
33
+ Project dependencies:
34
+
35
+ - [axios](https://www.npmjs.com/package/axios)
36
+ - [m3u8stream](https://www.npmjs.com/package/m3u8stream)
37
+
38
+ Development tooling used by the project:
39
+
40
+ - [@types/node](https:/t/www.npmjs.com/package/@types/node)
41
+ - [mocha](https://www.npmjs.com/package/mocha)
42
+ - [rollup](https://www.npmjs.com/package/rollup)
43
+ - [typescript](https://www.npmjs.com/package/typescript)
44
+
45
+
46
+ ## Installation
47
+
48
+ Install the package with npm:
49
+
50
+ ```bash
51
+ npm install @khang07/zing-mp3-api
52
+ ```
53
+
54
+ Build the project from source:
55
+
56
+ ```bash
57
+ npm run build
58
+ ```
59
+
60
+ Run tests:
61
+
62
+ ```bash
63
+ npm test
64
+ ```
65
+
66
+ Run live integration tests:
67
+
68
+ ```bash
69
+ ZING_MP3_LIVE=1 npm test
70
+ ```
71
+
72
+
73
+ ## Configuration
74
+
75
+ The library works out of the box with the default exported client.
76
+ If you need more control, create a `Client` instance with custom options.
77
+
78
+ ```ts
79
+ import { Client, Cookies } from "@khang07/zing-mp3-api";
80
+
81
+ const client = new Client({
82
+ maxLoad: 1024 * 1024,
83
+ maxHighWaterMark: 16 * 1024,
84
+ userAgent:
85
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " +
86
+ "(KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
87
+ jar: new Cookies()
88
+ });
89
+ ```
90
+
91
+ `ClientOptions` fields:
92
+
93
+ - `maxLoad`: request rate limit passed to Axios as `maxRate`
94
+ - `maxHighWaterMark`: stream buffer size used by `musicSync()` and `videoSync()`
95
+ - `userAgent`: custom request user-agent string
96
+ - `jar`: custom cookie jar instance
97
+
98
+ There is no additional post-install configuration required.
99
+ The client automatically fetches and stores cookies before protected requests.
100
+
101
+
102
+ ## Usage
103
+
104
+ ### ESM
105
+
106
+ ```ts
107
+ import client, { Client } from "@khang07/zing-mp3-api";
108
+ ```
109
+
110
+ ### CommonJS
111
+
112
+ ```js
113
+ const { default: client, Client } = require("@khang07/zing-mp3-api");
114
+ ```
115
+
116
+ ### Search for songs
117
+
118
+ ```ts
119
+ import client from "@khang07/zing-mp3-api";
120
+
121
+ const items = await client.searchMusic("Skyfall");
122
+ console.log(items[0]);
123
+ ```
124
+
125
+ ### Fetch playlist details
126
+
127
+ ```ts
128
+ import client from "@khang07/zing-mp3-api";
129
+
130
+ const playlist = await client.playlist(
131
+ "https://zingmp3.vn/album/example/ZWZB9WAB.html"
132
+ );
133
+
134
+ console.log(playlist.name);
135
+ console.log(playlist.mediaCount);
136
+ ```
137
+
138
+ ### Fetch artist details
139
+
140
+ ```ts
141
+ import client from "@khang07/zing-mp3-api";
142
+
143
+ const artist = await client.artist("https://zingmp3.vn/Obito");
144
+
145
+ console.log(artist.name);
146
+ console.log(artist.followCount);
147
+ ```
148
+
149
+ ### Download a music stream
150
+
151
+ ```ts
152
+ import { createWriteStream } from "node:fs";
153
+ import client from "@khang07/zing-mp3-api";
154
+
155
+ const items = await client.searchMusic("Example");
156
+ const stream = await client.music(items[0].id);
157
+
158
+ stream.pipe(createWriteStream("music.mp3"));
159
+ ```
160
+
161
+ ### Download a video stream
162
+
163
+ ```ts
164
+ import { createWriteStream } from "node:fs";
165
+ import client from "@khang07/zing-mp3-api";
166
+
167
+ const items = await client.searchVideo("Example");
168
+ const stream = await client.video(items[0].id);
169
+
170
+ stream.pipe(createWriteStream("video.ts"));
171
+ ```
172
+
173
+ ### Use the immediate stream APIs
174
+
175
+ ```ts
176
+ import { createWriteStream } from "node:fs";
177
+ import client from "@khang07/zing-mp3-api";
178
+
179
+ const stream = client.musicSync("Example ID");
180
+ stream.pipe(createWriteStream("music.mp3"));
181
+ ```
182
+
183
+ ### Handle library errors
184
+
185
+ ```ts
186
+ import client, { Lapse } from "@khang07/zing-mp3-api";
187
+
188
+ try {
189
+ await client.playlist("");
190
+ } catch (error) {
191
+ if (error instanceof Lapse) {
192
+ console.error(error.name);
193
+ console.error(error.code);
194
+ console.error(error.status);
195
+ console.error(error.cause);
196
+ }
197
+ }
198
+ ```
199
+
200
+
201
+ ## API reference
202
+
203
+ ### `new Client(options?)`
204
+
205
+ Creates a new client instance.
206
+
207
+ ### `Client.getIDFromURL(url)`
208
+
209
+ Extracts a resource token or alias from a Zing MP3 URL.
210
+ Throws `ERROR_INVALID_URL` when the input is empty or unsupported.
211
+
212
+ ### Instance methods
213
+
214
+ | Method | Returns | Description |
215
+ | --- | --- | --- |
216
+ | `video(videoID)` | `Promise<Readable>` | Fetch a video stream from a raw ID, URL string, or `URL` object. |
217
+ | `videoSync(videoID)` | `Readable` | Return a `PassThrough` immediately and pipe the resolved video stream into it. |
218
+ | `music(musicID)` | `Promise<Readable>` | Fetch a music stream from a raw ID, URL string, or `URL` object. |
219
+ | `musicSync(musicID)` | `Readable` | Return a `PassThrough` immediately and pipe the resolved music stream into it. |
220
+ | `playlist(playlistID)` | `Promise<PlayList>` | Fetch playlist details. |
221
+ | `artist(aliasID)` | `Promise<Artist>` | Fetch artist details. |
222
+ | `mediaDetails(mediaID)` | `Promise<Media>` | Fetch song details. |
223
+ | `searchMusic(query)` | `Promise<SearchMedia[]>` | Search songs. |
224
+ | `searchVideo(query)` | `Promise<SearchMedia[]>` | Search videos. |
225
+ | `searchList(query)` | `Promise<SearchPlayList[]>` | Search playlists. |
226
+ | `searchArtist(query)` | `Promise<SearchArtist[]>` | Search artists. |
227
+
228
+ ### Method notes
229
+
230
+ - `video()` prefers `720p` and falls back to `360p`
231
+ - `music()` prefers `320kbps` when available and falls back to `128kbps`
232
+ - If the main music endpoint returns a VIP-only response, `music()` retries the
233
+ extra music endpoint before throwing `ERROR_MUSIC_VIP_ONLY`
234
+ - Search methods currently request the first page with `count: 20`
235
+ - `musicSync()` and `videoSync()` forward stream failures to the returned
236
+ `PassThrough` and destroy the source stream when the output is closed
237
+
238
+ ### Data types
239
+
240
+ Public type exports from the root entry:
241
+
242
+ - `ClientOptions`
243
+ - `Artist`
244
+ - `Media`
245
+ - `PlayList`
246
+ - `SearchArtist`
247
+ - `SearchMedia`
248
+ - `SearchPlayList`
249
+
250
+
251
+ ## Public exports
252
+
253
+ ### Root export
254
+
255
+ Runtime exports:
256
+
257
+ - `default`: a ready-to-use `Client` instance
258
+ - `Client`: the client class
259
+ - `Cookies`: the cookies class
260
+ - `createSignature`: signature function
261
+ - `Lapse`: the custom error class
262
+
263
+ Type exports:
264
+
265
+ - `ClientOptions`
266
+ - `Artist`
267
+ - `Media`
268
+ - `PlayList`
269
+ - `SearchArtist`
270
+ - `SearchMedia`
271
+ - `SearchPlayList`
272
+
273
+ ### `Cookies` helper
274
+
275
+ Methods:
276
+
277
+ - `setCookie(setCookie, requestUrl)`
278
+ - `setCookies(setCookies, requestUrl)`
279
+ - `getCookies(requestUrl)`
280
+ - `getCookieHeader(requestUrl)`
281
+ - `applyToHeaders(requestUrl, headers?)`
282
+ - `deleteCookie(domain, path, name)`
283
+ - `cleanup()`
284
+ - `toJSON()`
285
+ - `fromJSON(cookies)`
286
+
287
+ Cookie record shape:
288
+
289
+ ```ts
290
+ interface CookieRecord {
291
+ name: string;
292
+ value: string;
293
+ domain: string;
294
+ path: string;
295
+ expiresAt?: number;
296
+ secure: boolean;
297
+ httpOnly: boolean;
298
+ sameSite?: "Strict" | "Lax" | "None";
299
+ hostOnly: boolean;
300
+ }
301
+ ```
302
+
303
+ Example:
304
+
305
+ ```ts
306
+ import { Cookies } from "@khang07/zing-mp3-api";
307
+
308
+ const jar = new Cookies();
309
+ jar.setCookie("sid=abc; Path=/; HttpOnly", "https://zingmp3.vn/");
310
+
311
+ console.log(jar.getCookieHeader("https://zingmp3.vn/"));
312
+ ```
313
+
314
+
315
+ ## Error handling
316
+
317
+ The library throws `Lapse`, a custom error type with the following fields:
318
+
319
+ - `name`
320
+ - `message`
321
+ - `code`
322
+ - `status`
323
+ - `cause`
324
+
325
+ Known library error codes:
326
+
327
+ - `ERROR_ARTIST_FETCH`
328
+ - `ERROR_ARTIST_NOT_FOUND`
329
+ - `ERROR_INVALID_ID`
330
+ - `ERROR_INVALID_QUERY`
331
+ - `ERROR_INVALID_URL`
332
+ - `ERROR_MEDIA_FETCH`
333
+ - `ERROR_MEDIA_NOT_FOUND`
334
+ - `ERROR_MUSIC_FETCH`
335
+ - `ERROR_MUSIC_VIP_ONLY`
336
+ - `ERROR_PLAYLIST_FETCH`
337
+ - `ERROR_PLAYLIST_NOT_FOUND`
338
+ - `ERROR_SEARCH_FAILED`
339
+ - `ERROR_SEARCH_FETCH`
340
+ - `ERROR_STREAM_DOWNLOAD`
341
+ - `ERROR_STREAM_URL_NOT_FOUND`
342
+ - `ERROR_VIDEO_FETCH`
343
+ - `ERROR_VIDEO_NOT_FOUND`
344
+
345
+
346
+ ## Troubleshooting & FAQ
347
+
348
+ ### `video()` returns a stream, but saved files do not open as MP4
349
+
350
+ Video playback is fetched from an HLS source via `m3u8stream`. If you write the
351
+ stream directly to disk, save it as a transport-stream style output such as
352
+ `.ts`, or post-process it with your own media pipeline.
353
+
354
+ ### Music lookup fails with `ERROR_MUSIC_VIP_ONLY`
355
+
356
+ Some tracks are restricted. The client already retries the fallback music
357
+ endpoint, but a playable URL may still be unavailable.
358
+
359
+ ### I want to reuse cookies between sessions
360
+
361
+ Use `jar.toJSON()` to persist cookies and `jar.fromJSON()` to restore them.
362
+ This is useful if you want to avoid starting from an empty cookie jar each time.
363
+
364
+ ### Input URLs fail with `ERROR_INVALID_URL`
365
+
366
+ Use either a standard Zing MP3 HTML URL such as
367
+ `https://zingmp3.vn/bai-hat/test/ZWZB9WAB.html` or a root-style artist URL such
368
+ as `https://zingmp3.vn/mono`.
369
+
370
+
371
+ ## Maintainers
372
+
373
+ - GiaKhang - [GiaKhang1810](https://github.com/GiaKhang1810)
374
+
375
+
376
+ ## License
377
+
378
+ This project is licensed under the MIT License.
379
+ See the [LICENSE](./LICENSE) file for details.