fsd-vod 0.14.0 → 0.15.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2023 郑州渺漠信息科技有限公司
3
+ Copyright (c) 2026 Liang Xingchen https://github.com/liangxingchen
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,32 +1,169 @@
1
1
  # fsd-vod
2
2
 
3
- FSD VOD文件读写适配器。
3
+ 阿里云 VOD(视频点播)适配器,专用于视频上传和管理。
4
4
 
5
- ```js
5
+ ## 配置
6
+
7
+ ```javascript
6
8
  const FSD = require('fsd');
7
9
  const VodAdapter = require('fsd-vod');
8
10
 
9
- const adapter = new VodAdapter(config);
11
+ const adapter = new VodAdapter({
12
+ accessKeyId: 'your-key',
13
+ accessKeySecret: 'your-secret',
14
+ region: 'cn-shanghai',
15
+ privateKey: 'your-private-key', // 必需
16
+ urlPrefix: 'https://your-domain.com'
17
+ });
18
+
10
19
  const fsd = FSD({ adapter: adapter });
20
+ ```
21
+
22
+ ### 配置选项说明
23
+
24
+ | 选项 | 类型 | 必需 | 默认值 | 说明 |
25
+ |------|------|------|--------|------|
26
+ | `accessKeyId` | string | 是 | - | 阿里云访问凭证 |
27
+ | `accessKeySecret` | string | 是 | - | 阿里云访问凭证 |
28
+ | `region` | string | 否 | 'cn-shanghai' | 区域代码 |
29
+ | `privateKey` | string | 是 | - | 视频上传签名私钥(必需) |
30
+ | `templateGroupId` | string | 否 | - | 模板组 ID |
31
+ | `workflowId` | string | 否 | - | 工作流 ID |
32
+ | `urlPrefix` | string | 否 | - | URL 前缀,用于生成访问链接 |
33
+ | `callbackUrl` | string | 否 | - | 回调 URL |
34
+
35
+ ### 重要说明
36
+
37
+ #### 1. 视频路径使用规则
38
+
39
+ VOD 适配器**不支持直接指定文件路径**,必须先通过 `adapter.alloc()` 分配视频 ID,然后使用该 ID 作为路径。
40
+
41
+ **为什么需要视频 ID?**
42
+
43
+ 阿里云 VOD 不像传统的文件系统那样有目录结构,每个视频上传前需要在 VOD 服务端预先分配一个唯一的视频 ID。这个 ID 会用于后续的上传、转码、播放等操作。
44
+
45
+ **正确的使用方式:**
46
+
47
+ ```javascript
48
+ // ✅ 正确:先分配视频 ID
49
+ const videoId = await fsd.adapter.alloc({
50
+ name: 'my-video.mp4',
51
+ metadata: {
52
+ title: 'My Video',
53
+ description: 'Video description'
54
+ }
55
+ });
56
+
57
+ // 使用返回的视频 ID 创建文件对象
58
+ const file = fsd(videoId);
59
+ // videoId 格式类似: 'ea5e82e7e8c54d3c8a5d8c5d8c5d8c5d'
60
+
61
+ // 上传视频
62
+ await file.write(videoBuffer);
63
+ ```
64
+
65
+ **错误的使用方式:**
66
+
67
+ ```javascript
68
+ // ❌ 错误:不能直接指定路径
69
+ const file = fsd('/videos/my-video.mp4');
70
+ await file.write(videoBuffer); // Error: 无法直接写入
71
+
72
+ // ❌ 错误:路径必须是由 alloc() 返回的视频 ID
73
+ const file = fsd('my-video.mp4');
74
+ await file.write(videoBuffer); // Error: 视频不存在
75
+ ```
11
76
 
12
- let videoId = await adapter.alloc({ name: 'test.mp4' });
13
- let file = fsd(videoId);
77
+ **alloc() 方法说明:**
14
78
 
15
- await fsd.write(data);
79
+ ```javascript
80
+ // alloc() 返回视频 ID
81
+ const videoId = await fsd.adapter.alloc(options);
16
82
 
83
+ // options 参数:
84
+ {
85
+ name: string, // 必需:视频文件名(如 'video.mp4')
86
+ metadata?: { // 可选:视频元数据
87
+ title?: string, // 视频标题
88
+ description?: string, // 视频描述
89
+ tags?: string[], // 视频标签
90
+ category?: string, // 视频分类
91
+ // ... 其他自定义元数据
92
+ }
93
+ }
17
94
  ```
18
95
 
19
- FSD 文档: https://github.com/liangxingchen/fsd
96
+ **文件对象属性:**
20
97
 
21
- 适配器初始化选项:
98
+ ```javascript
99
+ const videoId = await fsd.adapter.alloc({ name: 'video.mp4' });
100
+ const file = fsd(videoId);
22
101
 
23
- | 选项 | 类型 | 必须 | 说明 |
24
- | --------------- | ---------------- | ---- | ---------------------------- |
25
- | urlPrefix | string | | URL前缀,用于生成下载链接 |
26
- | accessKeyId | string | Yes | Vod访问KEY |
27
- | accessKeySecret | string | Yes | Vod访问秘钥 |
28
- | region | string | | 默认 cn-shanghai |
29
- | templateGroupId | string | | 转码模板组ID |
30
- | workflowId | string | | 工作流ID |
31
- | callbackUrl | string | | 边缘上传后的回调地址 |
102
+ console.log(file.path); // 视频 ID: 'ea5e82e7e8c54d3c8a5d8c5d8c5d8c5d'
103
+ console.log(file.dir); // '/'
104
+ console.log(file.base); // 视频 ID
105
+ console.log(file.name); // 视频 ID(无扩展名)
106
+ console.log(file.ext); // ''
107
+ ```
108
+
109
+ ---
110
+
111
+ #### 2. 不支持的目录操作
112
+
113
+ 由于阿里云 VOD 是对象存储服务,没有传统的目录概念,因此以下目录操作方法**不支持**:
114
+
115
+ - `mkdir()` - 创建目录
116
+ - `readdir()` - 列出目录
117
+ - `copy()` - 复制文件/目录
118
+ - `rename()` - 重命名/移动文件/目录
119
+
120
+ **不支持的操作及原因:**
121
+
122
+ | 方法 | 不支持原因 |
123
+ |------|-----------|
124
+ | `mkdir()` | VOD 没有目录概念,无需创建目录 |
125
+ | `readdir()` | 无法遍历 VOD 服务中的视频列表,需调用阿里云 VOD API |
126
+ | `copy()` | VOD 不支持在服务端复制视频 |
127
+ | `rename()` | VOD 不支持在服务端重命名视频 |
128
+
129
+ **调用这些方法会发生什么?**
130
+
131
+ ```javascript
132
+ // ❌ 创建目录
133
+ const dir = fsd('/videos/');
134
+ await dir.mkdir(); // Error: mkdir not supported
135
+
136
+ // ❌ 列出目录
137
+ const files = await dir.readdir(); // Error: readdir not supported
138
+
139
+ // ❌ 复制视频
140
+ const file = fsd(videoId);
141
+ await file.copy(videoId2); // Error: copy not supported
142
+
143
+ // ❌ 重命名视频
144
+ await file.rename(newVideoId); // Error: rename not supported
145
+ ```
146
+
147
+ **如何列出 VOD 中的视频?**
148
+
149
+ 需要直接调用阿里云 VOD API 来查询视频列表:
150
+
151
+ ```javascript
152
+ // 使用阿里云 VOD SDK 查询视频列表
153
+ const vod = require('@alicloud/vod20170321');
154
+ const client = new vod.default({
155
+ accessKeyId: 'your-key',
156
+ accessKeySecret: 'your-secret',
157
+ region: 'cn-shanghai'
158
+ });
159
+
160
+ // 查询视频列表
161
+ const result = await client.searchMedia({
162
+ searchType: 'video',
163
+ pageNo: 1,
164
+ pageSize: 50
165
+ });
166
+
167
+ console.log(result.mediaList); // 视频列表
168
+ ```
32
169
 
package/index.d.ts CHANGED
@@ -1,138 +1,801 @@
1
1
  import { Adapter } from 'fsd';
2
2
 
3
+ /**
4
+ * VODAdapter 配置选项
5
+ *
6
+ * 阿里云 VOD(视频点播)适配器的初始化配置。
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * const adapter = new VODAdapter({
11
+ * accessKeyId: 'your-access-key-id',
12
+ * accessKeySecret: 'your-access-key-secret',
13
+ * privateKey: 'your-rsa-private-key'
14
+ * });
15
+ * ```
16
+ */
3
17
  export interface VODAdapterOptions {
18
+ /**
19
+ * URL 前缀(可选)
20
+ *
21
+ * 用于生成播放链接时添加前缀。
22
+ * 通常配合 CDN 使用。
23
+ *
24
+ * @example
25
+ * ```typescript
26
+ * urlPrefix: 'https://cdn.example.com',
27
+ * // createUrl() 返回: https://cdn.example.com/video/HD
28
+ * ```
29
+ */
4
30
  urlPrefix?: string;
31
+
5
32
  /**
6
- * 是否公开读,默认为false
33
+ * 是否公共读(可选)
34
+ *
35
+ * 控制生成的播放 URL 是否需要签名访问。
36
+ *
37
+ * - `true`: 公共视频,生成直接访问 URL(无需签名)
38
+ * - `false`: 私有视频,生成带签名的临时 URL(默认值)
39
+ *
40
+ * @remarks
41
+ * 如果不公共读,必须提供 `privateKey` 用于生成签名。
7
42
  */
8
43
  publicRead?: boolean;
44
+
9
45
  /**
10
- * 非公开读时,生成URL时的鉴权秘钥
46
+ * 私有访问签名密钥(可选)
47
+ *
48
+ * 用于生成私有视频播放 URL 的签名。
49
+ *
50
+ * @example
51
+ * ```typescript
52
+ * privateKey: '-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----'
53
+ * ```
54
+ *
55
+ * @remarks
56
+ * RSA 私钥用于生成播放 URL 的签名。
57
+ * 请妥善保管此密钥,不要提交到代码仓库。
58
+ * 建议使用环境变量存储。
11
59
  */
12
60
  privateKey?: string;
61
+
62
+ /**
63
+ * OSS 访问 Key ID(必需)
64
+ *
65
+ * 阿里云 OSS 访问凭证(VOD 依赖 OSS 存储视频文件)。
66
+ *
67
+ * @example
68
+ * ```typescript
69
+ * accessKeyId: 'LTAI5txxxxxxxxxxxxx'
70
+ * ```
71
+ */
13
72
  accessKeyId: string;
73
+
74
+ /**
75
+ * OSS 访问 Key Secret(必需)
76
+ *
77
+ * 阿里云 OSS 访问凭证。
78
+ */
14
79
  accessKeySecret: string;
80
+
81
+ /**
82
+ * OSS 区域代码(可选)
83
+ *
84
+ * 阿里云 OSS 区域,如 'oss-cn-hangzhou'。
85
+ * 默认值:'cn-shanghai'
86
+ *
87
+ * @example
88
+ * ```typescript
89
+ * region: 'oss-cn-hangzhou', // 华东1(杭州)
90
+ * region: 'oss-cn-shanghai', // 华东2(上海)
91
+ * ```
92
+ */
15
93
  region?: string;
94
+
95
+ /**
96
+ * 转码模板组 ID(可选)
97
+ *
98
+ * 用于视频转码时的模板组。
99
+ *
100
+ * @example
101
+ * ```typescript
102
+ * templateGroupId: 'your-template-group-id'
103
+ * ```
104
+ *
105
+ * @remarks
106
+ * 模板组在阿里云 VOD 控制台创建。
107
+ */
16
108
  templateGroupId?: string;
109
+
110
+ /**
111
+ * 工作流 ID(可选)
112
+ *
113
+ * 用于视频上传后的自动化处理流程。
114
+ *
115
+ * @example
116
+ * ```typescript
117
+ * workflowId: 'your-workflow-id'
118
+ * ```
119
+ *
120
+ * @remarks
121
+ * 工作流在阿里云 VOD 控制台创建。
122
+ * 可以包括转码、截图、AI 处理等步骤。
123
+ */
17
124
  workflowId?: string;
125
+
126
+ /**
127
+ * 上传回调 URL(可选)
128
+ *
129
+ * 视频上传完成后,VOD 会调用此 URL 通知应用。
130
+ *
131
+ * @example
132
+ * ```typescript
133
+ * callbackUrl: 'https://api.example.com/vod/callback'
134
+ * ```
135
+ *
136
+ * @remarks
137
+ * 回调 body 包含视频 ID、状态等信息。
138
+ */
18
139
  callbackUrl?: string;
19
140
  }
20
141
 
142
+ /**
143
+ * 视频信息
144
+ *
145
+ * 视频的基本信息和状态。
146
+ *
147
+ * @example
148
+ * ```typescript
149
+ * {
150
+ * VideoId: '1234567890abcdef',
151
+ * Title: 'My Video',
152
+ * Status: 'Normal',
153
+ * Size: 1048576,
154
+ * Duration: '10.5'
155
+ * }
156
+ * ```
157
+ */
21
158
  export interface VideoInfo {
159
+ /**
160
+ * 审核状态
161
+ */
22
162
  AuditStatus: string;
163
+
164
+ /**
165
+ * 下载开关
166
+ */
23
167
  DownloadSwitch: string;
168
+
169
+ /**
170
+ * 预处理状态
171
+ */
24
172
  PreprocessStatus: string;
173
+
174
+ /**
175
+ * 修改时间
176
+ */
25
177
  ModificationTime: string;
178
+
179
+ /**
180
+ * 视频 ID
181
+ */
26
182
  VideoId: string;
183
+
184
+ /**
185
+ * 应用 ID
186
+ */
27
187
  AppId: string;
188
+
189
+ /**
190
+ * 修改时间
191
+ */
28
192
  ModifyTime: string;
193
+
194
+ /**
195
+ * 视频标题
196
+ */
29
197
  Title: string;
198
+
199
+ /**
200
+ * 创建时间
201
+ */
30
202
  CreationTime: string;
203
+
204
+ /**
205
+ * 状态
206
+ */
31
207
  Status: string;
208
+
209
+ /**
210
+ * 转码模板组 ID
211
+ */
32
212
  TemplateGroupId: string;
213
+
214
+ /**
215
+ * 区域 ID
216
+ */
33
217
  RegionId: string;
218
+
219
+ /**
220
+ * 视频时长(秒)
221
+ */
34
222
  Duration: number;
223
+
224
+ /**
225
+ * 创建时间
226
+ */
35
227
  CreateTime: string;
228
+
229
+ /**
230
+ * 快照列表
231
+ */
36
232
  Snapshots: { Snapshot: string[] };
233
+
234
+ /**
235
+ * 存储位置
236
+ */
37
237
  StorageLocation: string;
238
+
239
+ /**
240
+ * 视频大小(字节)
241
+ */
38
242
  Size: number;
39
243
  }
40
244
 
245
+ /**
246
+ * 媒体信息
247
+ *
248
+ * 视频转码后的媒体信息(不同清晰度)。
249
+ *
250
+ * @example
251
+ * ```typescript
252
+ * {
253
+ * VideoId: '1234567890abcdef',
254
+ * OutputType: 'MP4',
255
+ * PreprocessStatus: 'PreprocessingSuccess',
256
+ * FileURL: 'https://vod.oss-cn-hangzhou.aliyuncs.com/.../video.mp4',
257
+ * Duration: '10.5',
258
+ * Width: 1920,
259
+ * Height: 1080,
260
+ * Size: 1048576,
261
+ * CreationTime: '2024-01-01T00:00:00Z'
262
+ * }
263
+ * ```
264
+ */
41
265
  export interface MezzanineInfo {
266
+ /**
267
+ * 预处理状态
268
+ */
42
269
  PreprocessStatus: string;
270
+
271
+ /**
272
+ * 视频 ID
273
+ */
43
274
  VideoId: string;
275
+
276
+ /**
277
+ * 输出类型
278
+ */
44
279
  OutputType: string;
280
+
281
+ /**
282
+ * CRC64 校验码
283
+ */
45
284
  CRC64: string;
285
+
286
+ /**
287
+ * 创建时间
288
+ */
46
289
  CreationTime: string;
290
+
291
+ /**
292
+ * 状态
293
+ */
47
294
  Status: string;
295
+
296
+ /**
297
+ * 文件名
298
+ */
48
299
  FileName: string;
300
+
301
+ /**
302
+ * 时长(秒)
303
+ */
49
304
  Duration: string;
305
+
306
+ /**
307
+ * 视频高度
308
+ */
50
309
  Height: number;
310
+
311
+ /**
312
+ * 视频宽度
313
+ */
51
314
  Width: number;
315
+
316
+ /**
317
+ * 帧率
318
+ */
52
319
  Fps: string;
320
+
321
+ /**
322
+ * 比特率
323
+ */
53
324
  Bitrate: string;
325
+
326
+ /**
327
+ * 文件 URL
328
+ */
54
329
  FileURL: string;
330
+
331
+ /**
332
+ * 文件大小(字节)
333
+ */
55
334
  Size: number;
56
335
  }
57
336
 
337
+ /**
338
+ * 视频基础信息
339
+ *
340
+ * 视频的元数据信息。
341
+ *
342
+ * @example
343
+ * ```typescript
344
+ * {
345
+ * VideoId: '1234567890abcdef',
346
+ * MediaType: 'video',
347
+ * Status: 'Normal',
348
+ * Title: 'My Video',
349
+ * Duration: '10.5'
350
+ * }
351
+ * ```
352
+ */
58
353
  export interface VideoBase {
354
+ /**
355
+ * 转码模式
356
+ */
59
357
  TranscodeMode: string;
358
+
359
+ /**
360
+ * 创建时间
361
+ */
60
362
  CreationTime: string;
363
+
364
+ /**
365
+ * 封面图 URL
366
+ */
61
367
  CoverURL: string;
368
+
369
+ /**
370
+ * 状态
371
+ */
62
372
  Status: string;
373
+
374
+ /**
375
+ * 媒体类型
376
+ */
63
377
  MediaType: string;
378
+
379
+ /**
380
+ * 视频 ID
381
+ */
64
382
  VideoId: string;
383
+
384
+ /**
385
+ * 时长(秒)
386
+ */
65
387
  Duration: string;
388
+
389
+ /**
390
+ * 输出类型
391
+ */
66
392
  OutputType: string;
393
+
394
+ /**
395
+ * 标题
396
+ */
67
397
  Title: string;
68
398
  }
69
399
 
400
+ /**
401
+ * 播放信息
402
+ *
403
+ * 视频的播放配置信息。
404
+ *
405
+ * @example
406
+ * ```typescript
407
+ * {
408
+ * RequestId: 'request-id',
409
+ * VideoBase: {
410
+ * PlayURL: 'https://vod.oss-cn-hangzhou.aliyuncs.com/.../video.m3u8',
411
+ * Height: 1080,
412
+ * Width: 1920,
413
+ * Duration: '10.5',
414
+ * Definition: 'HD'
415
+ * }
416
+ * }
417
+ * ```
418
+ */
70
419
  export interface PlayInfo {
420
+ /**
421
+ * 格式(如 'mp4', 'm3u8')
422
+ */
71
423
  Format: string;
424
+
425
+ /**
426
+ * 流类型(如 'HLS', 'DASH')
427
+ */
72
428
  StreamType: string;
429
+
430
+ /**
431
+ * 预处理状态
432
+ */
73
433
  PreprocessStatus: string;
434
+
435
+ /**
436
+ * 修改时间
437
+ */
74
438
  ModificationTime: string;
439
+
440
+ /**
441
+ * 规格
442
+ */
75
443
  Specification: string;
444
+
445
+ /**
446
+ * 视频高度
447
+ */
76
448
  Height: number;
449
+
450
+ /**
451
+ * 播放 URL
452
+ */
77
453
  PlayURL: string;
454
+
455
+ /**
456
+ * 码率类型
457
+ */
78
458
  NarrowBandType: string;
459
+
460
+ /**
461
+ * 创建时间
462
+ */
79
463
  CreationTime: string;
464
+
465
+ /**
466
+ * 状态
467
+ */
80
468
  Status: string;
469
+
470
+ /**
471
+ * 时长(秒)
472
+ */
81
473
  Duration: string;
474
+
475
+ /**
476
+ * 任务 ID
477
+ */
82
478
  JobId: string;
479
+
480
+ /**
481
+ * 加密标志
482
+ */
83
483
  Encrypt: number;
484
+
485
+ /**
486
+ * 视频宽度
487
+ */
84
488
  Width: number;
489
+
490
+ /**
491
+ * 帧率
492
+ */
85
493
  Fps: string;
494
+
495
+ /**
496
+ * 比特率
497
+ */
86
498
  Bitrate: string;
87
- Size: number;
499
+
500
+ /**
501
+ * 清晰度(如 'HD', 'SD', '4K')
502
+ */
88
503
  Definition: string;
504
+
505
+ /**
506
+ * 文件大小(字节)
507
+ */
508
+ Size: number;
89
509
  }
90
510
 
511
+ /**
512
+ * 播放信息结果
513
+ *
514
+ * 包含视频基础信息和播放信息列表。
515
+ *
516
+ * @example
517
+ * ```typescript
518
+ * {
519
+ * RequestId: 'request-id',
520
+ * VideoBase: {
521
+ * // ... 基础信息
522
+ * },
523
+ * PlayInfoList: {
524
+ * PlayInfo: [
525
+ * {
526
+ * Format: 'mp4',
527
+ * PlayURL: 'https://...',
528
+ * Definition: 'HD'
529
+ * },
530
+ * {
531
+ * Format: 'm3u8',
532
+ * PlayURL: 'https://...',
533
+ * Definition: 'SD'
534
+ * }
535
+ * ]
536
+ * }
537
+ * }
538
+ * ```
539
+ */
91
540
  export interface PlayInfoResult {
541
+ /**
542
+ * 请求 ID
543
+ */
92
544
  RequestId: string;
545
+
546
+ /**
547
+ * 视频基础信息
548
+ */
93
549
  VideoBase: VideoBase;
550
+
551
+ /**
552
+ * 播放信息列表
553
+ */
94
554
  PlayInfoList: {
555
+ /**
556
+ * 播放信息数组
557
+ *
558
+ * 可能包含多种格式(MP4、M3U8)或多种清晰度(HD、SD)的播放地址。
559
+ */
95
560
  PlayInfo: PlayInfo[];
96
561
  };
97
562
  }
98
563
 
564
+ /**
565
+ * 上传凭证
566
+ *
567
+ * 包含临时 OSS 访问凭证的返回值。
568
+ *
569
+ * @example
570
+ * ```typescript
571
+ * {
572
+ * auth: {
573
+ * accessKeyId: 'STS.xxxxxxxxxxxxxx',
574
+ * accessKeySecret: 'xxxxxxxxxxxxxxxxxxxxxxxx',
575
+ * stsToken: 'CAESxxxx...',
576
+ * bucket: 'my-bucket',
577
+ * endpoint: 'oss-cn-hangzhou.aliyuncs.com'
578
+ * },
579
+ * path: '/videos/video.mp4',
580
+ * expiration: 17001234567890
581
+ * }
582
+ * ```
583
+ */
99
584
  export interface UploadToken {
585
+ /**
586
+ * 认证信息
587
+ *
588
+ * 包含临时 OSS 访问凭证。
589
+ */
100
590
  auth: {
591
+ /**
592
+ * 临时 Access Key ID
593
+ */
101
594
  accessKeyId: string;
595
+
596
+ /**
597
+ * 临时 Access Key Secret
598
+ */
102
599
  accessKeySecret: string;
600
+
601
+ /**
602
+ * STS 临时 Token
603
+ */
103
604
  stsToken: string;
605
+
606
+ /**
607
+ * Bucket 名称
608
+ */
104
609
  bucket: string;
610
+
611
+ /**
612
+ * OSS 访问地址
613
+ */
105
614
  endpoint: string;
106
615
  };
616
+
617
+ /**
618
+ * 上传目标路径
619
+ */
107
620
  path: string;
621
+
622
+ /**
623
+ * 凭证过期时间(Unix 时间戳,秒)
624
+ */
108
625
  expiration: number;
626
+
627
+ /**
628
+ * 回调数据(可选)
629
+ */
109
630
  callback?: any;
110
631
  }
111
632
 
633
+ /**
634
+ * 带自动刷新的上传凭证
635
+ *
636
+ * 包含自动刷新 STS Token 的上传凭证。
637
+ *
638
+ * @example
639
+ * ```typescript
640
+ * {
641
+ * auth: {
642
+ * // ... 认证信息
643
+ * refreshSTSToken: [Function: refresh]
644
+ * },
645
+ * path: '/videos/video.mp4',
646
+ * expiration: 17001234567890
647
+ * }
648
+ * ```
649
+ *
650
+ * @remarks
651
+ * 当凭证即将过期时,调用 `refreshSTSToken()` 获取新的临时凭证。
652
+ * 适用于大文件上传,避免上传过程中凭证过期。
653
+ */
112
654
  export interface UploadTokenWithAutoRefresh {
655
+ /**
656
+ * 认证信息
657
+ */
113
658
  auth: {
659
+ /**
660
+ * 临时 Access Key ID
661
+ */
114
662
  accessKeyId: string;
663
+
664
+ /**
665
+ * 临时 Access Key Secret
666
+ */
115
667
  accessKeySecret: string;
668
+
669
+ /**
670
+ * STS 临时 Token
671
+ */
116
672
  stsToken: string;
673
+
674
+ /**
675
+ * Bucket 名称
676
+ */
117
677
  bucket: string;
678
+
679
+ /**
680
+ * OSS 访问地址
681
+ */
118
682
  endpoint: string;
683
+
684
+ /**
685
+ * 自动刷新 STS Token 的函数
686
+ *
687
+ * 调用此函数获取新的临时凭证。
688
+ *
689
+ * @example
690
+ * ```typescript
691
+ * const newAuth = await auth.refreshSTSToken();
692
+ * console.log(newAuth.stsToken);
693
+ * ```
694
+ */
119
695
  refreshSTSToken: () => Promise<{
120
696
  accessKeyId: string;
121
697
  accessKeySecret: string;
122
698
  stsToken: string;
123
699
  }>;
124
700
  };
701
+
702
+ /**
703
+ * 上传目标路径
704
+ */
125
705
  path: string;
706
+
707
+ /**
708
+ * 凭证过期时间
709
+ */
126
710
  expiration: number;
711
+
712
+ /**
713
+ * 回调数据(可选)
714
+ */
127
715
  callback?: any;
128
716
  }
129
717
 
130
- export default class VODAdpter extends Adapter<VODAdapterOptions> {
718
+ /**
719
+ * 阿里云 VOD 适配器
720
+ *
721
+ * 提供对阿里云 VOD(视频点播)的访问能力。
722
+ *
723
+ * @remarks
724
+ * ### 核心特性
725
+ * - 视频上传
726
+ * - 视频信息查询
727
+ * - 播放 URL 生成(支持多清晰度)
728
+ * - STS 临时凭证(边缘上传)
729
+ * - Token 自动刷新
730
+ * - 转码模板和工作流支持
731
+ *
732
+ * ### 与 OSS 的关系
733
+ * - VOD 适配器内部依赖 `fsd-oss` 的 `SimpleOSSClient` 进行文件上传
734
+ * - 视频文件实际存储在 OSS 中
735
+ * - VOD 提供转码、截图等视频处理服务
736
+ *
737
+ * ### 不支持的操作
738
+ * - `mkdir()` - VOD 无目录概念
739
+ * - `readdir()` - VOD 无目录列表
740
+ * - `copy()` - VOD 不支持直接复制
741
+ * - `rename()` - VOD 不支持重命名
742
+ *
743
+ * ### alloc() 机制
744
+ * - 上传前必须先调用 `adapter.alloc({ name: 'video.mp4' })`
745
+ * - 返回视频 ID 作为文件路径
746
+ * - 视频 ID 格式:`/{VideoId}`
747
+ * - 不能直接使用 `fsd('/path')`
748
+ *
749
+ * @example
750
+ * ```typescript
751
+ * import VODAdapter from 'fsd-vod';
752
+ * import FSD from 'fsd';
753
+ *
754
+ * const adapter = new VODAdapter({
755
+ * accessKeyId: process.env.VOD_ACCESS_KEY_ID,
756
+ * accessKeySecret: process.env.VOD_ACCESS_KEY_SECRET,
757
+ * privateKey: process.env.VOD_PRIVATE_KEY
758
+ * });
759
+ *
760
+ * const fsd = FSD({ adapter });
761
+ *
762
+ * // ✅ 正确:先分配视频 ID
763
+ * const videoId = await adapter.alloc({ name: 'video.mp4' });
764
+ * const file = fsd(videoId);
765
+ * await file.write(videoData);
766
+ *
767
+ * // ❌ 错误:直接使用路径
768
+ * const file = fsd('/uploads/video.mp4'); // 会创建新视频而非上传到指定 ID
769
+ * await file.write(videoData);
770
+ * ```
771
+ */
772
+ export default class VODAdapter extends Adapter<VODAdapterOptions> {
131
773
  /**
132
774
  * 创建上传凭证
133
- * @param {string} videoId 视频ID
134
- * @param {any} [meta] 文件元信息
135
- * @param {number} [durationSeconds] 上传凭证有效期,单位秒, 默认 3600
775
+ *
776
+ * 生成用于客户端直接上传到 VOD 的临时凭证。
777
+ *
778
+ * @param videoId - 视频 ID(由 `alloc()` 返回)
779
+ * @param meta - 文件元数据(可选)
780
+ * @param durationSeconds - 凭证有效期(秒),默认 3600(1 小时)
781
+ * @returns 上传凭证对象
782
+ *
783
+ * @example
784
+ * ```typescript
785
+ * // 生成 1 小时有效的上传凭证
786
+ * const token = await adapter.createUploadToken(
787
+ * '/1234567890abcdef',
788
+ * { contentType: 'video/mp4' },
789
+ * 3600
790
+ * );
791
+ *
792
+ * // 将 token 发送给前端
793
+ * res.json({ uploadToken: token });
794
+ * ```
795
+ *
796
+ * @remarks
797
+ * 生成的凭证用于客户端直接上传到 OSS。
798
+ * 上传完成后,VOD 会处理转码等操作。
136
799
  */
137
800
  createUploadToken: (
138
801
  videoId: string,
@@ -142,9 +805,30 @@ export default class VODAdpter extends Adapter<VODAdapterOptions> {
142
805
 
143
806
  /**
144
807
  * 创建带自动刷新的上传凭证
145
- * @param {string} videoId 视频ID
146
- * @param {any} [meta] 文件元信息
147
- * @param {number} [durationSeconds] 上传凭证有效期,单位秒, 默认 3600
808
+ *
809
+ * 生成支持自动刷新 STS Token 的上传凭证。
810
+ *
811
+ * @param videoId - 视频 ID(由 `alloc()` 返回)
812
+ * @param meta - 文件元数据(可选)
813
+ * @param durationSeconds - 凭证有效期(秒),默认 3600(1 小时)
814
+ * @returns 带自动刷新功能的上传凭证对象
815
+ *
816
+ * @example
817
+ * ```typescript
818
+ * const token = await adapter.createUploadTokenWithAutoRefresh(
819
+ * '/1234567890abcdef',
820
+ * { contentType: 'video/mp4' },
821
+ * 3600
822
+ * );
823
+ *
824
+ * // 当凭证快过期时,自动刷新
825
+ * const newAuth = await token.auth.refreshSTSToken();
826
+ * console.log(newAuth.stsToken);
827
+ * ```
828
+ *
829
+ * @remarks
830
+ * 适用于大文件上传,避免上传过程中凭证过期。
831
+ * 使用 LRUCache 缓存 Token,减少刷新次数。
148
832
  */
149
833
  createUploadTokenWithAutoRefresh: (
150
834
  videoId: string,
@@ -154,21 +838,68 @@ export default class VODAdpter extends Adapter<VODAdapterOptions> {
154
838
 
155
839
  /**
156
840
  * 获取视频信息
157
- * @param {string} videoId 视频ID
841
+ *
842
+ * 查询视频的基本信息(标题、状态、时长等)。
843
+ *
844
+ * @param videoId - 视频 ID
845
+ * @returns 视频信息,如果视频不存在则返回 null
846
+ *
847
+ * @example
848
+ * ```typescript
849
+ const info = await adapter.getVideoInfo('1234567890abcdef');
850
+ if (info) {
851
+ * console.log('Title:', info.Title);
852
+ * console.log('Status:', info.Status);
853
+ * console.log('Duration:', info.Duration);
854
+ * }
855
+ * ```
158
856
  */
159
857
  getVideoInfo(videoId: string): Promise<null | VideoInfo>;
160
858
 
161
859
  /**
162
- * 获取视频播放信息
163
- * @param {string} videoId 视频ID
164
- * @param {any} [options] 参数选项
860
+ * 获取视频媒体信息
861
+ *
862
+ * 查询视频转码后的媒体信息(不同清晰度)。
863
+ *
864
+ * @param videoId - 视频 ID
865
+ * @param options - 可选参数
866
+ * @returns 媒体信息,如果不存在则返回 null
867
+ *
868
+ * @example
869
+ * ```typescript
870
+ const mezzanineInfo = await adapter.getMezzanineInfo('1234567890abcdef');
871
+ if (mezzanineInfo) {
872
+ * console.log('FileURL:', mezzanineInfo.FileURL);
873
+ * console.log('Duration:', mezzanineInfo.Duration);
874
+ * console.log('Size:', mezzanineInfo.Size);
875
+ * }
876
+ * ```
165
877
  */
166
878
  getMezzanineInfo(videoId: string, options?: any): Promise<null | MezzanineInfo>;
167
879
 
168
880
  /**
169
881
  * 获取视频播放信息
170
- * @param {string} videoId 视频ID
171
- * @param {any} [options] 参数选项
882
+ *
883
+ * 查询视频的播放配置信息(不同格式和清晰度的播放地址)。
884
+ *
885
+ * @param videoId - 视频 ID
886
+ * @param options - 可选参数
887
+ * @returns 播放信息结果
888
+ *
889
+ * @example
890
+ * ```typescript
891
+ * const playInfo = await adapter.getPlayInfo('1234567890abcdef');
892
+ console.log('PlayInfoList:', playInfo.PlayInfoList);
893
+ *
894
+ // PlayInfoList 包含多种格式和清晰度
895
+ // - Format: mp4, m3u8
896
+ // - Definition: HD, SD, FHD, 4K
897
+ * - PlayURL: 每种配置的播放地址
898
+ * ```
899
+ *
900
+ * @remarks
901
+ * 返回的信息可能包含多个清晰度(HD、SD、FHD、4K)的播放地址。
902
+ * 可以根据用户网络和设备选择合适的清晰度。
172
903
  */
173
904
  getPlayInfo(videoId: string, options?: any): Promise<null | PlayInfoResult>;
174
905
  }
package/lib/index.js CHANGED
@@ -1,13 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- const crypto = require("crypto");
3
+ const tslib_1 = require("tslib");
4
+ const crypto_1 = tslib_1.__importDefault(require("crypto"));
4
5
  const stream_1 = require("stream");
5
6
  const lru_cache_1 = require("lru-cache");
6
- const Debugger = require("debug");
7
- const RPC = require("@alicloud/pop-core");
8
- const akita_1 = require("akita");
9
- const simple_oss_client_1 = require("fsd-oss/simple-oss-client");
10
- const debug = Debugger('fsd-vod');
7
+ const debug_1 = tslib_1.__importDefault(require("debug"));
8
+ const pop_core_1 = tslib_1.__importDefault(require("@alicloud/pop-core"));
9
+ const akita_1 = tslib_1.__importDefault(require("akita"));
10
+ const simple_oss_client_1 = tslib_1.__importDefault(require("fsd-oss/simple-oss-client"));
11
+ const debug = (0, debug_1.default)('fsd-vod');
11
12
  const client = akita_1.default.resolve('fsd-vod');
12
13
  const CALLBACK_BODY = 'bucket=${bucket}&path=${object}&etag=${etag}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}&format=${imageInfo.format}';
13
14
  function parseToken(result) {
@@ -35,7 +36,7 @@ function parseVideoId(id) {
35
36
  return { id, path };
36
37
  }
37
38
  function md5(str) {
38
- return crypto.createHash('md5').update(str).digest('hex');
39
+ return crypto_1.default.createHash('md5').update(str).digest('hex');
39
40
  }
40
41
  class VODAdapter {
41
42
  constructor(options) {
@@ -57,7 +58,7 @@ class VODAdapter {
57
58
  max: 1000,
58
59
  ttl: 60000
59
60
  });
60
- this._rpc = new RPC({
61
+ this._rpc = new pop_core_1.default({
61
62
  accessKeyId: options.accessKeyId,
62
63
  accessKeySecret: options.accessKeySecret,
63
64
  endpoint: `https://vod.${options.region || 'cn-shanghai'}.aliyuncs.com`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fsd-vod",
3
- "version": "0.14.0",
3
+ "version": "0.15.0",
4
4
  "description": "Aliyun OSS adapter for fsd",
5
5
  "main": "lib/index.js",
6
6
  "types": "index.d.ts",
@@ -9,14 +9,18 @@
9
9
  "prepublish": "npm run build"
10
10
  },
11
11
  "repository": "https://github.com/liangxingchen/fsd/tree/master/packages/fsd-vod",
12
- "author": "Liang <liang@miaomo.cc> (https://github.com/liangxingchen)",
12
+ "author": {
13
+ "name": "Liang",
14
+ "email": "liang@miaomo.cn",
15
+ "url": "https://github.com/liangxingchen"
16
+ },
13
17
  "license": "MIT",
14
18
  "dependencies": {
15
19
  "@alicloud/pop-core": "^1.8.0",
16
- "akita": "^1.1.0",
17
- "debug": "^4.4.0",
18
- "fsd-oss": "^0.14.0",
19
- "lru-cache": "^11.0.2"
20
+ "akita": "^1.2.0",
21
+ "debug": "^4.4.3",
22
+ "fsd-oss": "^0.15.0",
23
+ "lru-cache": "^11.2.4"
20
24
  },
21
- "gitHead": "74e32bf47242909f040eb6012dda56e5c5a668a0"
25
+ "gitHead": "ea754919fb95b49deffd529b5c01c66da0dc08f9"
22
26
  }