@francofantomius/jellyfin 0.4.0 → 0.4.2
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 +1 -1
- package/dist/index.cjs +31 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -6
- package/dist/index.d.ts +12 -6
- package/dist/index.js +31 -10
- package/dist/index.js.map +1 -1
- package/llms.txt +542 -0
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ A modern, lightweight, modular, and type-safe Jellyfin SDK for Node.js and the B
|
|
|
6
6
|
[](https://francofantomius.github.io/jellyfin-sdk/)
|
|
7
7
|
[](LICENSE)
|
|
8
8
|
|
|
9
|
-
**Current Version: `v0.4.
|
|
9
|
+
**Current Version: `v0.4.2`**
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
---
|
package/dist/index.cjs
CHANGED
|
@@ -27,7 +27,10 @@ var JellyfinAuthError = class extends JellyfinApiError {
|
|
|
27
27
|
// src/http.ts
|
|
28
28
|
function cleanUrl(url) {
|
|
29
29
|
if (!url) return "";
|
|
30
|
-
let cleaned = url.trim()
|
|
30
|
+
let cleaned = url.trim();
|
|
31
|
+
while (cleaned.endsWith("/")) {
|
|
32
|
+
cleaned = cleaned.slice(0, -1);
|
|
33
|
+
}
|
|
31
34
|
if (!/^https?:\/\//i.test(cleaned)) {
|
|
32
35
|
cleaned = "https://" + cleaned;
|
|
33
36
|
}
|
|
@@ -1188,6 +1191,7 @@ var MediaModule = class {
|
|
|
1188
1191
|
const qualityResolved = resolveAudioQuality(options.quality);
|
|
1189
1192
|
const maxStreamingBitrate = options.maxStreamingBitrate ?? qualityResolved.maxStreamingBitrate;
|
|
1190
1193
|
const {
|
|
1194
|
+
mediaSourceId,
|
|
1191
1195
|
static: isStatic = false,
|
|
1192
1196
|
startTimeTicks = 0,
|
|
1193
1197
|
container = "opus,mp3|mp3,aac,m4a,m4b,flac,wav,ogg",
|
|
@@ -1198,12 +1202,14 @@ var MediaModule = class {
|
|
|
1198
1202
|
const endpoint = isStatic ? `/Audio/${itemId}/stream` : `/Audio/${itemId}/universal`;
|
|
1199
1203
|
const url = new URL(`${serverUrl}${endpoint}`);
|
|
1200
1204
|
if (token && useQueryToken) {
|
|
1205
|
+
url.searchParams.append("ApiKey", token);
|
|
1201
1206
|
url.searchParams.append("api_key", token);
|
|
1202
1207
|
url.searchParams.append("X-Emby-Token", token);
|
|
1203
1208
|
}
|
|
1204
1209
|
if (userId) url.searchParams.append("UserId", userId);
|
|
1205
1210
|
const targetDeviceId = options.deviceId !== void 0 ? options.deviceId : clientInfo.deviceId;
|
|
1206
1211
|
if (targetDeviceId) url.searchParams.append("DeviceId", targetDeviceId);
|
|
1212
|
+
if (mediaSourceId) url.searchParams.append("MediaSourceId", mediaSourceId);
|
|
1207
1213
|
if (isStatic) {
|
|
1208
1214
|
url.searchParams.append("static", "true");
|
|
1209
1215
|
} else {
|
|
@@ -1234,6 +1240,7 @@ var MediaModule = class {
|
|
|
1234
1240
|
const qualityResolved = resolveAudioQuality(options.quality);
|
|
1235
1241
|
const maxStreamingBitrate = options.maxStreamingBitrate ?? qualityResolved.maxStreamingBitrate;
|
|
1236
1242
|
const {
|
|
1243
|
+
mediaSourceId,
|
|
1237
1244
|
startTimeTicks = 0,
|
|
1238
1245
|
audioCodec = "aac,mp3",
|
|
1239
1246
|
segmentLength = 3,
|
|
@@ -1241,13 +1248,16 @@ var MediaModule = class {
|
|
|
1241
1248
|
} = options;
|
|
1242
1249
|
const url = new URL(`${serverUrl}/Audio/${itemId}/master.m3u8`);
|
|
1243
1250
|
if (token && useQueryToken) {
|
|
1251
|
+
url.searchParams.append("ApiKey", token);
|
|
1244
1252
|
url.searchParams.append("api_key", token);
|
|
1245
1253
|
url.searchParams.append("X-Emby-Token", token);
|
|
1246
1254
|
}
|
|
1247
1255
|
if (userId) url.searchParams.append("UserId", userId);
|
|
1248
1256
|
const targetDeviceId = options.deviceId !== void 0 ? options.deviceId : clientInfo.deviceId;
|
|
1249
1257
|
if (targetDeviceId) url.searchParams.append("DeviceId", targetDeviceId);
|
|
1250
|
-
|
|
1258
|
+
if (mediaSourceId) {
|
|
1259
|
+
url.searchParams.append("MediaSourceId", mediaSourceId);
|
|
1260
|
+
}
|
|
1251
1261
|
url.searchParams.append("AudioCodec", audioCodec);
|
|
1252
1262
|
url.searchParams.append("TranscodingContainer", "ts");
|
|
1253
1263
|
url.searchParams.append("TranscodingProtocol", "hls");
|
|
@@ -1293,6 +1303,7 @@ var MediaModule = class {
|
|
|
1293
1303
|
const endpoint = container && !container.includes(",") ? `/Videos/${itemId}/stream.${container}` : `/Videos/${itemId}/stream`;
|
|
1294
1304
|
const url = new URL(`${serverUrl}${endpoint}`);
|
|
1295
1305
|
if (token && useQueryToken) {
|
|
1306
|
+
url.searchParams.append("ApiKey", token);
|
|
1296
1307
|
url.searchParams.append("api_key", token);
|
|
1297
1308
|
url.searchParams.append("X-Emby-Token", token);
|
|
1298
1309
|
}
|
|
@@ -1358,6 +1369,7 @@ var MediaModule = class {
|
|
|
1358
1369
|
} = options;
|
|
1359
1370
|
const url = new URL(`${serverUrl}/Videos/${itemId}/master.m3u8`);
|
|
1360
1371
|
if (token && useQueryToken) {
|
|
1372
|
+
url.searchParams.append("ApiKey", token);
|
|
1361
1373
|
url.searchParams.append("api_key", token);
|
|
1362
1374
|
url.searchParams.append("X-Emby-Token", token);
|
|
1363
1375
|
}
|
|
@@ -1400,6 +1412,7 @@ var MediaModule = class {
|
|
|
1400
1412
|
const sourceId = mediaSourceId || itemId;
|
|
1401
1413
|
const url = new URL(`${serverUrl}/Videos/${itemId}/${sourceId}/Subtitles/${subtitleStreamIndex}/Stream.${format}`);
|
|
1402
1414
|
if (token && useQueryToken) {
|
|
1415
|
+
url.searchParams.append("ApiKey", token);
|
|
1403
1416
|
url.searchParams.append("api_key", token);
|
|
1404
1417
|
}
|
|
1405
1418
|
if (startTimeTicks > 0) {
|
|
@@ -1473,6 +1486,7 @@ var MediaModule = class {
|
|
|
1473
1486
|
if (!serverUrl || !itemId) return "";
|
|
1474
1487
|
const url = new URL(`${serverUrl}/Items/${itemId}/Download`);
|
|
1475
1488
|
if (token && useQueryToken) {
|
|
1489
|
+
url.searchParams.append("ApiKey", token);
|
|
1476
1490
|
url.searchParams.append("api_key", token);
|
|
1477
1491
|
}
|
|
1478
1492
|
if (filename) {
|
|
@@ -1519,13 +1533,17 @@ var MediaModule = class {
|
|
|
1519
1533
|
/**
|
|
1520
1534
|
* Returns authentication headers for media streaming requests (e.g., configuring hls.js xhrSetup).
|
|
1521
1535
|
* Crucial for Jellyfin 12+ servers where query-based auth (?api_key=) is disabled.
|
|
1536
|
+
* Pass { legacy: false } to only send Authorization and avoid conflicting headers or reverse-proxy CORS rejections.
|
|
1522
1537
|
*/
|
|
1523
|
-
getStreamHeaders() {
|
|
1538
|
+
getStreamHeaders(options = {}) {
|
|
1524
1539
|
const authHeader = this.http.getAuthHeader();
|
|
1525
|
-
|
|
1526
|
-
Authorization: authHeader
|
|
1527
|
-
"X-Emby-Authorization": authHeader
|
|
1540
|
+
const headers = {
|
|
1541
|
+
Authorization: authHeader
|
|
1528
1542
|
};
|
|
1543
|
+
if (options.legacy ?? true) {
|
|
1544
|
+
headers["X-Emby-Authorization"] = authHeader;
|
|
1545
|
+
}
|
|
1546
|
+
return headers;
|
|
1529
1547
|
}
|
|
1530
1548
|
};
|
|
1531
1549
|
|
|
@@ -1662,7 +1680,7 @@ var OfflineStorageManager = class {
|
|
|
1662
1680
|
request.onerror = () => resolve(null);
|
|
1663
1681
|
});
|
|
1664
1682
|
} catch (err) {
|
|
1665
|
-
console.warn(
|
|
1683
|
+
console.warn("[Jellyfin Offline] getDownloadedRecord error for %s:", id, err);
|
|
1666
1684
|
return null;
|
|
1667
1685
|
}
|
|
1668
1686
|
}
|
|
@@ -2417,7 +2435,7 @@ var WebSocketModule = class {
|
|
|
2417
2435
|
try {
|
|
2418
2436
|
listener(...args);
|
|
2419
2437
|
} catch (e) {
|
|
2420
|
-
console.error(
|
|
2438
|
+
console.error("[Jellyfin SDK] WebSocket listener error on '%s':", event, e);
|
|
2421
2439
|
}
|
|
2422
2440
|
});
|
|
2423
2441
|
}
|
|
@@ -2857,6 +2875,7 @@ var TrickplayModule = class {
|
|
|
2857
2875
|
const { mediaSourceId, useQueryToken = true } = options;
|
|
2858
2876
|
const url = new URL(`${serverUrl}/Videos/${itemId}/Trickplay/${width}/tiles.m3u8`);
|
|
2859
2877
|
if (token && useQueryToken) {
|
|
2878
|
+
url.searchParams.append("ApiKey", token);
|
|
2860
2879
|
url.searchParams.append("api_key", token);
|
|
2861
2880
|
url.searchParams.append("X-Emby-Token", token);
|
|
2862
2881
|
}
|
|
@@ -2875,6 +2894,7 @@ var TrickplayModule = class {
|
|
|
2875
2894
|
const { mediaSourceId, useQueryToken = true } = options;
|
|
2876
2895
|
const url = new URL(`${serverUrl}/Videos/${itemId}/Trickplay/${width}/${index}.jpg`);
|
|
2877
2896
|
if (token && useQueryToken) {
|
|
2897
|
+
url.searchParams.append("ApiKey", token);
|
|
2878
2898
|
url.searchParams.append("api_key", token);
|
|
2879
2899
|
url.searchParams.append("X-Emby-Token", token);
|
|
2880
2900
|
}
|
|
@@ -3065,6 +3085,7 @@ var ChaptersModule = class {
|
|
|
3065
3085
|
} = options;
|
|
3066
3086
|
const url = new URL(`${serverUrl}/Items/${itemId}/Images/Chapter/${chapterIndex}`);
|
|
3067
3087
|
if (token && useQueryToken) {
|
|
3088
|
+
url.searchParams.append("ApiKey", token);
|
|
3068
3089
|
url.searchParams.append("api_key", token);
|
|
3069
3090
|
url.searchParams.append("X-Emby-Token", token);
|
|
3070
3091
|
}
|
|
@@ -3749,7 +3770,7 @@ var JellyfinClient = class {
|
|
|
3749
3770
|
this.cache = new MemoryCacheAdapter();
|
|
3750
3771
|
this.clientInfo = {
|
|
3751
3772
|
name: options.clientInfo?.name || "@francofantomius/jellyfin",
|
|
3752
|
-
version: options.clientInfo?.version || "0.4.
|
|
3773
|
+
version: options.clientInfo?.version || "0.4.2",
|
|
3753
3774
|
device: options.clientInfo?.device || (typeof window !== "undefined" ? "Web Browser" : "Node.js"),
|
|
3754
3775
|
deviceId: options.clientInfo?.deviceId || generateDeviceId()
|
|
3755
3776
|
};
|
|
@@ -3871,7 +3892,7 @@ var JellyfinClient = class {
|
|
|
3871
3892
|
try {
|
|
3872
3893
|
fn(...args);
|
|
3873
3894
|
} catch (e) {
|
|
3874
|
-
console.error(
|
|
3895
|
+
console.error("[Jellyfin SDK] Event handler error on '%s':", event, e);
|
|
3875
3896
|
}
|
|
3876
3897
|
});
|
|
3877
3898
|
}
|