UNPKG

node-m3u8-downloader

Version:

Downloads an m3u8 playlist to specified output

117 lines (95 loc) 3.39 kB
const fs = require('fs'); const fsp = require('fs/promises'); const path = require('path'); const url = require('node:url'); const dl = require('./download'); const error = require('./error'); module.exports = function segment(segments = [], streamUrl, cache = path.join(require('os').tmpdir(), 'm3u8dl'), maxConnections = 20, cb = console.log) { return new Promise(async (resolve) => { cb('start'); let total = segments.length; let current = 0; segments = segments.map(v => { let uri = v.uri || v.url; if (!isUrl(uri)) { v.uri = new url.URL(uri, streamUrl).href; } return v; }); const semaphore = (max) => { let active = 0; const waiting = []; const take = () => new Promise(res => { if (active < max) { active++; res(); } else { waiting.push(res); } }); const release = () => { if (waiting.length > 0) { waiting.shift()(); } else { active--; } }; return { take, release }; }; const sem = semaphore(maxConnections); const retryDownloadSegment = async (index, retries = 3) => { let seg = segments[index]; let parsedUrl = url.parse(seg.uri || seg.url, false); let pathName = parsedUrl.pathname; let extension = path.extname(pathName); let segmentPath = path.join(cache, `segment-${index}${extension}`); await sem.take(); try { let dl_r = await dl(seg.uri, segmentPath); if (dl_r !== 100) { return resolve(dl_r); } segments[index].path = segmentPath; current += 1; cb('progress', { uri: seg.uri || seg.url, path: segmentPath, progress: { total: total, current: current, percentage: Math.floor((current / total) * 100), }, }); } catch (e) { if (retries > 0) { await new Promise(res => setTimeout(res, 1000 * Math.pow(2, 3 - retries))); // Exponential backoff return retryDownloadSegment(index, retries - 1); } else { return resolve(e); } } finally { sem.release(); } }; try { await Promise.all(segments.map((_, index) => retryDownloadSegment(index))); let returnData = { totalSegments: total, segments, path: cache }; cb('end', returnData); return resolve(returnData); } catch (e) { return resolve(new error(e)); } }); }; function isUrl(input) { try { new url.URL(input); return true; } catch (_) { return false; } }