@lzwme/m3u8-dl 1.1.0 → 1.1.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/cjs/lib/m3u8-convert.js +6 -4
- package/cjs/lib/m3u8-download.js +2 -0
- package/cjs/server/download-server.js +1 -2
- package/cjs/types/m3u8.d.ts +2 -0
- package/client/index.html +92 -108
- package/client/play.html +44 -0
- package/package.json +1 -1
package/cjs/lib/m3u8-convert.js
CHANGED
|
@@ -17,10 +17,10 @@ async function m3u8Convert(options, data) {
|
|
|
17
17
|
(0, fe_utils_1.mkdirp)((0, node_path_1.dirname)(filepath));
|
|
18
18
|
if (ffmpegSupport) {
|
|
19
19
|
const ffconcatFile = (0, node_path_1.resolve)((0, node_path_1.dirname)(data[0].tsOut), 'ffconcat.txt');
|
|
20
|
-
let filesAllArr = data.
|
|
20
|
+
let filesAllArr = data.filter(d => (0, node_fs_1.existsSync)(d.tsOut)).map(d => `file '${d.tsOut}'\nduration ${d.duration}`);
|
|
21
21
|
if (process.platform === 'win32')
|
|
22
22
|
filesAllArr = filesAllArr.map(d => d.replaceAll('\\', '/'));
|
|
23
|
-
(0, node_fs_1.writeFileSync)(ffconcatFile, 'ffconcat version 1.0\
|
|
23
|
+
(0, node_fs_1.writeFileSync)(ffconcatFile, 'ffconcat version 1.0\n' + filesAllArr.join('\n'));
|
|
24
24
|
let headersString = '';
|
|
25
25
|
if (options.headers) {
|
|
26
26
|
for (const [key, value] of Object.entries(options.headers)) {
|
|
@@ -28,13 +28,15 @@ async function m3u8Convert(options, data) {
|
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
// ffmpeg -i nz.ts -c copy -map 0:v -map 0:a -bsf:a aac_adtstoasc nz.mp4
|
|
31
|
-
const cmd = `ffmpeg -y -f concat -safe 0 -i "${ffconcatFile}" -acodec copy -vcodec copy -bsf:a aac_adtstoasc ${headersString} "${filepath}"`;
|
|
31
|
+
// const cmd = `ffmpeg -async 1 -y -f concat -safe 0 -i "${ffconcatFile}" -acodec copy -vcodec copy -bsf:a aac_adtstoasc ${headersString} "${filepath}"`;
|
|
32
|
+
const cmd = `ffmpeg -async 1 -y -f concat -safe 0 -i "${ffconcatFile}" -c:v copy -c:a copy -movflags +faststart -fflags +genpts -bsf:a aac_adtstoasc ${headersString} "${filepath}"`;
|
|
32
33
|
utils_1.logger.debug('[convert to mp4]cmd:', (0, console_log_colors_1.cyan)(cmd));
|
|
33
34
|
const r = (0, fe_utils_1.execSync)(cmd);
|
|
34
35
|
ffmpegSupport = !r.error;
|
|
35
36
|
if (r.error)
|
|
36
37
|
utils_1.logger.error('Conversion to mp4 failed. Please confirm that `ffmpeg` is installed!', r.stderr);
|
|
37
|
-
|
|
38
|
+
else
|
|
39
|
+
(0, node_fs_1.unlinkSync)(ffconcatFile);
|
|
38
40
|
}
|
|
39
41
|
if (!ffmpegSupport) {
|
|
40
42
|
filepath = filepath.replace(/\.mp4$/, '.ts');
|
package/cjs/lib/m3u8-download.js
CHANGED
|
@@ -185,6 +185,7 @@ async function m3u8Download(url, options = {}) {
|
|
|
185
185
|
remainingTime: 0,
|
|
186
186
|
localM3u8: (0, local_play_js_1.toLocalM3u8)(m3u8Info.data).replace(options.cacheDir, '').replaceAll(node_path_1.sep, '/').slice(1),
|
|
187
187
|
filename: options.filename,
|
|
188
|
+
threadNum: options.threadNum,
|
|
188
189
|
};
|
|
189
190
|
const runTask = (data) => {
|
|
190
191
|
for (const info of data) {
|
|
@@ -234,6 +235,7 @@ async function m3u8Download(url, options = {}) {
|
|
|
234
235
|
if (stats.speed > stats.avgSpeed)
|
|
235
236
|
stats.remainingTime = stats.remainingTime * (stats.avgSpeed / stats.speed);
|
|
236
237
|
stats.remainingTime = Math.ceil(stats.remainingTime);
|
|
238
|
+
stats.size = stats.downloadedSize * (stats.duration / stats.durationDownloaded);
|
|
237
239
|
}
|
|
238
240
|
if (options.showProgress) {
|
|
239
241
|
const processBar = '='.repeat(Math.floor(stats.progress * 0.2)).padEnd(20, '-');
|
|
@@ -110,11 +110,10 @@ class DLServer {
|
|
|
110
110
|
item.status = 'pause';
|
|
111
111
|
}
|
|
112
112
|
else {
|
|
113
|
-
const isError = item.status === 'done' && item.options?.convert && (!item.localVideo || !(0, node_fs_1.existsSync)(item.localVideo));
|
|
113
|
+
const isError = item.status === 'done' && item.options?.convert !== false && (!item.localVideo || !(0, node_fs_1.existsSync)(item.localVideo));
|
|
114
114
|
if (isError) {
|
|
115
115
|
item.status = 'error';
|
|
116
116
|
item.errmsg = '本地视频文件不存在';
|
|
117
|
-
item.localVideo = '';
|
|
118
117
|
}
|
|
119
118
|
}
|
|
120
119
|
this.dlCache.set(url, item);
|
package/cjs/types/m3u8.d.ts
CHANGED
package/client/index.html
CHANGED
|
@@ -155,13 +155,11 @@
|
|
|
155
155
|
class="text-blue-500 hover:text-blue-600">https://github.com/lzwme/m3u8-dl.git</a>
|
|
156
156
|
</p>
|
|
157
157
|
<p class="text-gray-600"><strong>问题反馈:</strong>
|
|
158
|
-
<a href="https://github.com/lzwme/m3u8-dl/issues" target="_blank" rel="noopener"
|
|
159
|
-
class="text-blue-500 hover:text-blue-600">
|
|
158
|
+
<a href="https://github.com/lzwme/m3u8-dl/issues" target="_blank" rel="noopener" class="text-blue-500 hover:text-blue-600">
|
|
160
159
|
https://github.com/lzwme/m3u8-dl/issues</a>
|
|
161
160
|
</p>
|
|
162
161
|
<p class="text-gray-600"><strong>当前版本:</strong>
|
|
163
|
-
<a href="https://github.com/lzwme/m3u8-dl/release" target="_blank" rel="noopener"
|
|
164
|
-
class="text-blue-500 hover:text-blue-600">
|
|
162
|
+
<a href="https://github.com/lzwme/m3u8-dl/release" target="_blank" rel="noopener" class="text-blue-500 hover:text-blue-600">
|
|
165
163
|
{{serverInfo.version}}</a>
|
|
166
164
|
</p>
|
|
167
165
|
</div>
|
|
@@ -192,8 +190,7 @@
|
|
|
192
190
|
<h3 class="text-lg font-medium text-gray-900 mb-2">安装使用</h3>
|
|
193
191
|
<div class="bg-gray-50 p-4 rounded-lg">
|
|
194
192
|
<p class="text-gray-600 mb-2">全局安装:</p>
|
|
195
|
-
<pre
|
|
196
|
-
class="bg-gray-800 text-gray-100 p-3 rounded-lg overflow-x-auto">npm i -g @lzwme/m3u8-dl<br>m3u8dl -h</pre>
|
|
193
|
+
<pre class="bg-gray-800 text-gray-100 p-3 rounded-lg overflow-x-auto">npm i -g @lzwme/m3u8-dl<br>m3u8dl -h</pre>
|
|
197
194
|
<p class="text-gray-600 mt-4 mb-2">使用 npx:</p>
|
|
198
195
|
<pre class="bg-gray-800 text-gray-100 p-3 rounded-lg overflow-x-auto">npx @lzwme/m3u8-dl -h</pre>
|
|
199
196
|
</div>
|
|
@@ -259,8 +256,7 @@ services:
|
|
|
259
256
|
<label class="block text-sm font-bold text-gray-700 mb-1">下载完成后删除ts分片缓存</label>
|
|
260
257
|
<div class="flex items-center mt-2">
|
|
261
258
|
<label class="inline-flex items-center">
|
|
262
|
-
<input type="checkbox" v-model="config.delCache"
|
|
263
|
-
class="form-checkbox h-5 w-5 text-blue-500 rounded focus:ring-blue-500">
|
|
259
|
+
<input type="checkbox" v-model="config.delCache" class="form-checkbox h-5 w-5 text-blue-500 rounded focus:ring-blue-500">
|
|
264
260
|
<span class="ml-2 text-gray-700">删除分片文件</span>
|
|
265
261
|
</label>
|
|
266
262
|
</div>
|
|
@@ -272,8 +268,7 @@ services:
|
|
|
272
268
|
<label class="block text-sm font-bold text-gray-700 mb-1">下载完成后转换格式</label>
|
|
273
269
|
<div class="flex items-center mt-2">
|
|
274
270
|
<label class="inline-flex items-center">
|
|
275
|
-
<input type="checkbox" v-model="config.convert"
|
|
276
|
-
class="form-checkbox h-5 w-5 text-blue-500 rounded focus:ring-blue-500">
|
|
271
|
+
<input type="checkbox" v-model="config.convert" class="form-checkbox h-5 w-5 text-blue-500 rounded focus:ring-blue-500">
|
|
277
272
|
<span class="ml-2 text-gray-700">合并转换为 MP4/TS 文件</span>
|
|
278
273
|
</label>
|
|
279
274
|
</div>
|
|
@@ -324,8 +319,8 @@ services:
|
|
|
324
319
|
<div>
|
|
325
320
|
<label class="block text-sm font-bold text-gray-700 mb-1">访问密码</label>
|
|
326
321
|
<div class="flex">
|
|
327
|
-
<input v-model="token" type="password" maxlength="256"
|
|
328
|
-
|
|
322
|
+
<input v-model="token" type="password" maxlength="256" class="w-full p-2 border rounded-lg focus:ring-blue-500"
|
|
323
|
+
placeholder="请输入访问密码" />
|
|
329
324
|
</div>
|
|
330
325
|
<p class="mt-1 text-sm text-gray-500">若服务端设置了访问密码(token),请在此输入</p>
|
|
331
326
|
</div>
|
|
@@ -349,8 +344,7 @@ services:
|
|
|
349
344
|
<div class="flex justify-between items-center">
|
|
350
345
|
<h2 class="text-xl font-semibold">下载任务</h2>
|
|
351
346
|
<div class="flex space-x-2">
|
|
352
|
-
<button @click="showNewDownloadDialog"
|
|
353
|
-
class="px-3 py-1 text-sm bg-blue-500 hover:bg-blue-600 text-white rounded">
|
|
347
|
+
<button @click="showNewDownloadDialog" class="px-3 py-1 text-sm bg-blue-500 hover:bg-blue-600 text-white rounded">
|
|
354
348
|
<i class="fas fa-plus mr-1"></i>新建
|
|
355
349
|
</button>
|
|
356
350
|
<button v-if="selectedTasks.length > 0" @click="pauseDownload(selectedTasks)"
|
|
@@ -383,23 +377,22 @@ services:
|
|
|
383
377
|
<div class="flex-1">
|
|
384
378
|
<div class="relative">
|
|
385
379
|
<input type="text" v-model="searchQuery"
|
|
386
|
-
class="w-full pl-10 pr-4 py-2 border rounded-lg focus:ring-blue-500 focus:border-blue-500"
|
|
387
|
-
|
|
380
|
+
class="w-full pl-10 pr-4 py-2 border rounded-lg focus:ring-blue-500 focus:border-blue-500" placeholder="搜索任务名称或URL"
|
|
381
|
+
aria-label="搜索任务">
|
|
388
382
|
<i class="fas fa-search absolute left-3 top-3 text-gray-400"></i>
|
|
389
383
|
</div>
|
|
390
384
|
</div>
|
|
391
385
|
<div class="flex items-center space-x-2">
|
|
392
|
-
<select v-model="statusFilter"
|
|
393
|
-
class="px-3 py-2 border rounded-lg focus:ring-blue-500 focus:border-blue-500" title="按状态筛选"
|
|
386
|
+
<select v-model="statusFilter" class="px-3 py-2 border rounded-lg focus:ring-blue-500 focus:border-blue-500" title="按状态筛选"
|
|
394
387
|
aria-label="按状态筛选">
|
|
395
388
|
<option value="">全部状态</option>
|
|
396
389
|
<option value="resume">下载中</option>
|
|
397
390
|
<option value="pending">等待中</option>
|
|
398
391
|
<option value="pause">已暂停</option>
|
|
392
|
+
<option value="error">异常</option>
|
|
399
393
|
<option value="done">已完成</option>
|
|
400
394
|
</select>
|
|
401
|
-
<button @click="clearFilters" class="px-3 py-2 text-gray-600 hover:text-gray-800" title="清除筛选条件"
|
|
402
|
-
aria-label="清除筛选条件">
|
|
395
|
+
<button @click="clearFilters" class="px-3 py-2 text-gray-600 hover:text-gray-800" title="清除筛选条件" aria-label="清除筛选条件">
|
|
403
396
|
<i class="fas fa-times"></i>
|
|
404
397
|
</button>
|
|
405
398
|
</div>
|
|
@@ -431,32 +424,25 @@ services:
|
|
|
431
424
|
<div class="flex items-center justify-between mb-2">
|
|
432
425
|
<div class="flex-1">
|
|
433
426
|
<div class="flex items-center">
|
|
434
|
-
<input type="checkbox" :checked="selectedTasks.includes(task.url)"
|
|
435
|
-
@change="toggleTaskSelection(task.url)"
|
|
427
|
+
<input type="checkbox" :checked="selectedTasks.includes(task.url)" @change="toggleTaskSelection(task.url)"
|
|
436
428
|
class="form-checkbox h-5 w-5 text-blue-500 rounded focus:ring-blue-500 mr-2"
|
|
437
429
|
:aria-label="'选择任务:' + (task.localVideo || task.filename || task.url)">
|
|
438
430
|
<h3 class="font-bold text-green-600 truncate" :title="task.url">
|
|
439
431
|
{{ task.localVideo || task.filename || task.url }}
|
|
440
432
|
</h3>
|
|
441
|
-
<span v-if="task.status === 'pending'"
|
|
442
|
-
class="ml-2 px-2 py-0.5 text-xs bg-yellow-100 text-yellow-800 rounded">等待中</span>
|
|
433
|
+
<span v-if="task.status === 'pending'" class="ml-2 px-2 py-0.5 text-xs bg-yellow-100 text-yellow-800 rounded">等待中</span>
|
|
443
434
|
<span v-else-if="task.status === 'resume'"
|
|
444
435
|
class="ml-2 px-2 py-0.5 text-xs bg-green-100 text-green-800 rounded">下载中</span>
|
|
445
|
-
<span v-else-if="task.status === 'pause'"
|
|
446
|
-
|
|
447
|
-
<span v-else-if="task.status === '
|
|
448
|
-
|
|
449
|
-
<span v-else-if="task.status === 'error'"
|
|
450
|
-
class="ml-2 px-2 py-0.5 text-xs bg-red-100 text-red-600 rounded" :title="task.errmsg">异常<i
|
|
451
|
-
class="fas fa-info-circle ml-1"></i></span>
|
|
436
|
+
<span v-else-if="task.status === 'pause'" class="ml-2 px-2 py-0.5 text-xs bg-gray-100 text-gray-800 rounded">已暂停</span>
|
|
437
|
+
<span v-else-if="task.status === 'done'" class="ml-2 px-2 py-0.5 text-xs bg-blue-100 text-blue-800 rounded">已完成</span>
|
|
438
|
+
<span v-else-if="task.status === 'error'" class="ml-2 px-2 py-0.5 text-xs bg-red-100 text-red-600 rounded"
|
|
439
|
+
:title="task.errmsg">异常<i class="fas fa-info-circle ml-1"></i></span>
|
|
452
440
|
</div>
|
|
453
441
|
<div class="flex items-center text-sm text-gray-500 mt-1 flex-wrap gap-2">
|
|
454
|
-
<span v-if="config.showPreview" class="text-blue-500 hover:text-blue-600 cursor-pointer"
|
|
455
|
-
@click="preview(task.url)">
|
|
442
|
+
<span v-if="config.showPreview" class="text-blue-500 hover:text-blue-600 cursor-pointer" @click="preview(task.url)">
|
|
456
443
|
<i class="fas fa-eye mr-1"></i>预览
|
|
457
444
|
</span>
|
|
458
|
-
<span v-if="config.showLocalPlay" class="text-green-500 hover:text-green-600 cursor-pointer"
|
|
459
|
-
@click="localPlay(task)">
|
|
445
|
+
<span v-if="config.showLocalPlay" class="text-green-500 hover:text-green-600 cursor-pointer" @click="localPlay(task)">
|
|
460
446
|
<i class="fas fa-play-circle mr-1"></i>{{ task.localVideo ? '播放' : '边下边播' }}
|
|
461
447
|
</span>
|
|
462
448
|
<span v-if="task.duration" class="flex items-center">
|
|
@@ -491,17 +477,15 @@ services:
|
|
|
491
477
|
</div>
|
|
492
478
|
</div>
|
|
493
479
|
<div class="flex space-x-2">
|
|
494
|
-
<button v-if="task.status === 'resume' || task.status === 'pending'"
|
|
495
|
-
|
|
496
|
-
title="暂停">
|
|
480
|
+
<button v-if="task.status === 'resume' || task.status === 'pending'" @click="pauseDownload([task.url])"
|
|
481
|
+
class="p-2 text-yellow-500 hover:bg-yellow-50 rounded" title="暂停">
|
|
497
482
|
<i class="fas fa-pause"></i>
|
|
498
483
|
</button>
|
|
499
484
|
<button v-if="task.status === 'pause' || task.status === 'error'" @click="resumeDownload([task.url])"
|
|
500
485
|
class="p-2 text-green-500 hover:bg-green-50 rounded" title="继续">
|
|
501
486
|
<i class="fas fa-play"></i>
|
|
502
487
|
</button>
|
|
503
|
-
<button @click="deleteDownload([task.url])" class="p-2 text-red-500 hover:bg-red-50 rounded"
|
|
504
|
-
title="删除">
|
|
488
|
+
<button @click="deleteDownload([task.url])" class="p-2 text-red-500 hover:bg-red-50 rounded" title="删除">
|
|
505
489
|
<i class="fas fa-trash"></i>
|
|
506
490
|
</button>
|
|
507
491
|
</div>
|
|
@@ -524,8 +508,7 @@ services:
|
|
|
524
508
|
<div v-if="filteredTasks.length === 0" class="p-8 text-center text-gray-500">
|
|
525
509
|
<i class="fas fa-download text-4xl mb-4"></i>
|
|
526
510
|
<p>暂无下载任务</p>
|
|
527
|
-
<button @click="showNewDownloadDialog"
|
|
528
|
-
class="mt-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
|
|
511
|
+
<button @click="showNewDownloadDialog" class="mt-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
|
|
529
512
|
<i class="fas fa-plus mr-1"></i>添加下载任务
|
|
530
513
|
</button>
|
|
531
514
|
</div>
|
|
@@ -720,7 +703,7 @@ services:
|
|
|
720
703
|
const ws = new WebSocket(`ws://${location.host}/ws?token=${this.token}`);
|
|
721
704
|
this.ws = ws;
|
|
722
705
|
ws.onmessage = (e) => {
|
|
723
|
-
|
|
706
|
+
let { type, data } = T.safeJSONParse(e.data);
|
|
724
707
|
|
|
725
708
|
switch (type) {
|
|
726
709
|
case 'serverInfo':
|
|
@@ -877,7 +860,7 @@ services:
|
|
|
877
860
|
if (typeof urls === 'string') urls = [urls];
|
|
878
861
|
const r = await T.post(`/pause`, { urls, all: urls[0] === 'all' });
|
|
879
862
|
if (!r.code) T.toast(r.message || '已暂停下载');
|
|
880
|
-
this.
|
|
863
|
+
if (urls === this.selectedTasks) this.selectedTasks = [];
|
|
881
864
|
},
|
|
882
865
|
/** 恢复下载 */
|
|
883
866
|
resumeDownload: async function (urls) {
|
|
@@ -885,6 +868,60 @@ services:
|
|
|
885
868
|
if (typeof urls === 'string') urls = [urls];
|
|
886
869
|
const r = await T.post(`/resume`, { urls, all: urls[0] === 'all' });
|
|
887
870
|
if (!r.code) T.toast(r.message || '已恢复下载');
|
|
871
|
+
if (urls === this.selectedTasks) this.selectedTasks = [];
|
|
872
|
+
},
|
|
873
|
+
/** 删除选中的任务 */
|
|
874
|
+
async deleteDownload(urls = this.selectedTasks) {
|
|
875
|
+
if (!urls.length) return;
|
|
876
|
+
try {
|
|
877
|
+
const result = await Swal.fire({
|
|
878
|
+
title: '确认删除',
|
|
879
|
+
html: `
|
|
880
|
+
<div class="text-left">
|
|
881
|
+
<div class="mb-4">
|
|
882
|
+
<label class="inline-flex items-center">
|
|
883
|
+
<input type="checkbox" id="deleteCache" class="form-checkbox h-5 w-5 text-blue-500 rounded focus:ring-blue-500" checked>
|
|
884
|
+
<span class="ml-2 text-gray-700">同时删除已下载的缓存</span>
|
|
885
|
+
</label>
|
|
886
|
+
</div>
|
|
887
|
+
<div>
|
|
888
|
+
<label class="inline-flex items-center">
|
|
889
|
+
<input type="checkbox" id="deleteVideo" class="form-checkbox h-5 w-5 text-blue-500 rounded focus:ring-blue-500" checked>
|
|
890
|
+
<span class="ml-2 text-red-700">同时删除已下载的视频</span>
|
|
891
|
+
</label>
|
|
892
|
+
</div>
|
|
893
|
+
</div>
|
|
894
|
+
`,
|
|
895
|
+
showCancelButton: true,
|
|
896
|
+
confirmButtonText: '确认删除',
|
|
897
|
+
cancelButtonText: '取消',
|
|
898
|
+
confirmButtonColor: '#ef4444',
|
|
899
|
+
focusConfirm: false,
|
|
900
|
+
preConfirm: () => {
|
|
901
|
+
return {
|
|
902
|
+
deleteCache: document.getElementById('deleteCache').checked,
|
|
903
|
+
deleteVideo: document.getElementById('deleteVideo').checked
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
});
|
|
907
|
+
|
|
908
|
+
if (result.isConfirmed) {
|
|
909
|
+
const r = await T.post(`/delete`, {
|
|
910
|
+
urls,
|
|
911
|
+
deleteCache: result.value.deleteCache,
|
|
912
|
+
deleteVideo: result.value.deleteVideo
|
|
913
|
+
});
|
|
914
|
+
if (!r.code) {
|
|
915
|
+
T.toast(r.message || '已删除选中的下载');
|
|
916
|
+
urls.forEach(url => (delete this.tasks[url]));
|
|
917
|
+
if (urls === this.selectedTasks) this.selectedTasks = [];
|
|
918
|
+
this.forceUpdate();
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
} catch (error) {
|
|
922
|
+
console.error('删除下载失败:', error);
|
|
923
|
+
T.alert('删除下载失败: ' + error.message);
|
|
924
|
+
}
|
|
888
925
|
},
|
|
889
926
|
getTasks: async function () {
|
|
890
927
|
this.tasks = await T.get('/tasks');
|
|
@@ -893,17 +930,17 @@ services:
|
|
|
893
930
|
localPlay: function (task) {
|
|
894
931
|
const url = location.origin + '/localplay/' + encodeURIComponent(task.localVideo || task.localM3u8);
|
|
895
932
|
console.log(task);
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
}
|
|
933
|
+
// return window.open(`./play.html?url=${encodeURIComponent(url)}`);
|
|
934
|
+
Swal.fire({
|
|
935
|
+
title: task?.options.filename || task.url,
|
|
936
|
+
width: '1000px',
|
|
937
|
+
padding: 0,
|
|
938
|
+
allowOutsideClick: false,
|
|
939
|
+
showCloseButton: true,
|
|
940
|
+
showConfirmButton: false,
|
|
941
|
+
html: `<iframe src="./play.html?url=${encodeURIComponent(url)}" style="width: 100%; height: 550px; max-width: 1000px; max-height: 90vh" frameborder="0" allowfullscreen></iframe>`,
|
|
942
|
+
// html: `<video autoplay width="100%" style="max-width: 1000px; min-height: 300px; max-height: 90vh" src="${url}" controls></video> `,
|
|
943
|
+
});
|
|
907
944
|
},
|
|
908
945
|
// 预览
|
|
909
946
|
preview: function (url) {
|
|
@@ -969,59 +1006,6 @@ services:
|
|
|
969
1006
|
this.selectedTasks.splice(index, 1);
|
|
970
1007
|
}
|
|
971
1008
|
},
|
|
972
|
-
/** 删除选中的任务 */
|
|
973
|
-
async deleteDownload(urls = this.selectedTasks) {
|
|
974
|
-
if (!urls.length) return;
|
|
975
|
-
try {
|
|
976
|
-
const result = await Swal.fire({
|
|
977
|
-
title: '确认删除',
|
|
978
|
-
html: `
|
|
979
|
-
<div class="text-left">
|
|
980
|
-
<div class="mb-4">
|
|
981
|
-
<label class="inline-flex items-center">
|
|
982
|
-
<input type="checkbox" id="deleteCache" class="form-checkbox h-5 w-5 text-blue-500 rounded focus:ring-blue-500" checked>
|
|
983
|
-
<span class="ml-2 text-gray-700">同时删除已下载的缓存</span>
|
|
984
|
-
</label>
|
|
985
|
-
</div>
|
|
986
|
-
<div>
|
|
987
|
-
<label class="inline-flex items-center">
|
|
988
|
-
<input type="checkbox" id="deleteVideo" class="form-checkbox h-5 w-5 text-blue-500 rounded focus:ring-blue-500" checked>
|
|
989
|
-
<span class="ml-2 text-red-700">同时删除已下载的视频</span>
|
|
990
|
-
</label>
|
|
991
|
-
</div>
|
|
992
|
-
</div>
|
|
993
|
-
`,
|
|
994
|
-
showCancelButton: true,
|
|
995
|
-
confirmButtonText: '确认删除',
|
|
996
|
-
cancelButtonText: '取消',
|
|
997
|
-
confirmButtonColor: '#ef4444',
|
|
998
|
-
focusConfirm: false,
|
|
999
|
-
preConfirm: () => {
|
|
1000
|
-
return {
|
|
1001
|
-
deleteCache: document.getElementById('deleteCache').checked,
|
|
1002
|
-
deleteVideo: document.getElementById('deleteVideo').checked
|
|
1003
|
-
};
|
|
1004
|
-
}
|
|
1005
|
-
});
|
|
1006
|
-
|
|
1007
|
-
if (result.isConfirmed) {
|
|
1008
|
-
const r = await T.post(`/delete`, {
|
|
1009
|
-
urls,
|
|
1010
|
-
deleteCache: result.value.deleteCache,
|
|
1011
|
-
deleteVideo: result.value.deleteVideo
|
|
1012
|
-
});
|
|
1013
|
-
if (!r.code) {
|
|
1014
|
-
T.toast(r.message || '已删除选中的下载');
|
|
1015
|
-
urls.forEach(url => (delete this.tasks[url]));
|
|
1016
|
-
this.selectedTasks = [];
|
|
1017
|
-
this.forceUpdate();
|
|
1018
|
-
}
|
|
1019
|
-
}
|
|
1020
|
-
} catch (error) {
|
|
1021
|
-
console.error('删除下载失败:', error);
|
|
1022
|
-
T.alert('删除下载失败: ' + error.message);
|
|
1023
|
-
}
|
|
1024
|
-
},
|
|
1025
1009
|
},
|
|
1026
1010
|
mounted() {
|
|
1027
1011
|
T.reqHeaders.authorization = this.token ? `${this.token}` : '';
|
package/client/play.html
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<title>m3u8 在线播放</title>
|
|
6
|
+
<meta charset="utf-8">
|
|
7
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
8
|
+
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0,viewport-fit=cover" name="viewport" />
|
|
9
|
+
<link crossorigin="anonymous" rel="stylesheet" href="https://lib.baomitu.com/dplayer/latest/DPlayer.min.css">
|
|
10
|
+
<style>
|
|
11
|
+
body {
|
|
12
|
+
margin: 0;
|
|
13
|
+
}
|
|
14
|
+
</style>
|
|
15
|
+
</head>
|
|
16
|
+
|
|
17
|
+
<body>
|
|
18
|
+
<div id="dplayer"></div>
|
|
19
|
+
<script crossorigin="anonymous"
|
|
20
|
+
integrity="sha512-yi//c0pOEPlBEqUMgK7Ia1VXQT9TwuMHJIRU+T2lyV7YxsMhbF35N/DGYkCFWfC9ebjdupP4xadFyFVTz/sgEg=="
|
|
21
|
+
src="https://lib.baomitu.com/hls.js/1.3.0/hls.min.js"></script>
|
|
22
|
+
<script crossorigin="anonymous" src="https://lib.baomitu.com/dplayer/latest/DPlayer.min.js"></script>
|
|
23
|
+
|
|
24
|
+
<script>
|
|
25
|
+
const url = location.href.split('url=')[1];
|
|
26
|
+
if (!url) {
|
|
27
|
+
document.getElementById('dplayer').innerText = '请传入播放地址参数 url=';
|
|
28
|
+
} else {
|
|
29
|
+
const dp = new DPlayer({
|
|
30
|
+
container: document.getElementById('dplayer'),
|
|
31
|
+
autoplay: true,
|
|
32
|
+
video: {
|
|
33
|
+
url: decodeURIComponent(url),
|
|
34
|
+
type: 'auto',
|
|
35
|
+
},
|
|
36
|
+
pluginOptions: {
|
|
37
|
+
hls: {},
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
</script>
|
|
42
|
+
</body>
|
|
43
|
+
|
|
44
|
+
</html>
|