123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484 |
- import { _ } from './lib/cat.js';
- import * as HLS from './lib/hls.js';
- let key = 'ffm3u8';
- let url = '';
- let categories = [];
- let siteKey = '';
- let siteType = 0;
- async function request(reqUrl, agentSp) {
- let res = await req(reqUrl, {
- method: 'get',
- });
- return JSON.parse(res.content);
- }
- async function init(cfg) {
- siteKey = cfg.skey;
- siteType = cfg.stype;
- url = cfg.ext.url;
- categories = cfg.ext.categories;
- }
- async function home(filter) {
- const data = await request(url);
- let classes = [];
- for (const cls of data.class) {
- const n = cls.type_name.toString().trim();
- if (categories && categories.length > 0) {
- if (categories.indexOf(n) < 0) continue;
- }
- classes.push({
- type_id: cls.type_id.toString(),
- type_name: n,
- });
- }
- if (categories && categories.length > 0) {
- classes = _.sortBy(classes, (p) => {
- return categories.indexOf(p.type_name);
- });
- }
- return {
- class: classes,
- };
- }
- async function homeVod() {
- return '{}';
- }
- async function category(tid, pg, filter, extend) {
- let page = pg || 1;
- if (page == 0) page = 1;
- const data = await request(url + `?ac=detail&t=${tid}&pg=${page}`);
- let videos = [];
- for (const vod of data.list) {
- videos.push({
- vod_id: vod.vod_id.toString(),
- vod_name: vod.vod_name.toString(),
- vod_pic: vod.vod_pic,
- vod_remarks: vod.vod_remarks,
- });
- }
- return {
- page: parseInt(data.page),
- pagecount: data.pagecount,
- total: data.total,
- list: videos,
- };
- }
- async function detail(id) {
- const data = (await request(url + `?ac=detail&ids=${id}`)).list[0];
- let vod = {
- vod_id: data.vod_id,
- vod_name: data.vod_name,
- vod_pic: data.vod_pic,
- type_name: data.type_name,
- vod_year: data.vod_year,
- vod_area: data.vod_area,
- vod_remarks: data.vod_remarks,
- vod_actor: data.vod_actor,
- vod_director: data.vod_director,
- vod_content: data.vod_content.trim(),
- vod_play_from: data.vod_play_from,
- vod_play_url: data.vod_play_url,
- };
- return {
- list: [vod],
- };
- }
- async function proxy(segments, headers, reqHeaders) {
- let what = segments[0];
- let segs = decodeURIComponent(segments[1]);
- if (what == 'hls') {
- function hlsHeader(data, hls) {
- let hlsHeaders = {};
- if (data.headers['content-length']) {
- Object.assign(hlsHeaders, data.headers, { 'content-length': hls.length.toString() });
- } else {
- Object.assign(hlsHeaders, data.headers);
- }
- delete hlsHeaders['transfer-encoding'];
- if (hlsHeaders['content-encoding'] == 'gzip') {
- delete hlsHeaders['content-encoding'];
- }
- return hlsHeaders;
- }
- const hlsData = await hlsCache(segs, headers);
- if (hlsData.variants) {
- // variants -> variants -> .... ignore
- const hls = HLS.stringify(hlsData.plist);
- return {
- code: hlsData.code,
- content: hls,
- headers: hlsHeader(hlsData, hls),
- };
- } else {
- const hls = HLS.stringify(hlsData.plist, (segment) => {
- return js2Proxy(false, siteType, siteKey, 'ts/' + encodeURIComponent(hlsData.key + '/' + segment.mediaSequenceNumber.toString()), headers);
- });
- return {
- code: hlsData.code,
- content: hls,
- headers: hlsHeader(hlsData, hls),
- };
- }
- } else if (what == 'ts') {
- const info = segs.split('/');
- const hlsKey = info[0];
- const segIdx = parseInt(info[1]);
- return await tsCache(hlsKey, segIdx, headers);
- }
- return '{}';
- }
- async function play(flag, id, flags) {
- try {
- const pUrls = await hls2Urls(id, {});
- for (let index = 1; index < pUrls.length; index += 2) {
- pUrls[index] = js2Proxy(false, siteType, siteKey, 'hls/' + encodeURIComponent(pUrls[index]), {});
- }
- pUrls.push('original');
- pUrls.push(id);
- return {
- parse: 0,
- url: pUrls,
- };
- } catch (e) {
- return {
- parse: 0,
- url: id,
- };
- }
- }
- async function search(wd, quick, pg) {
- let page = pg || 1;
- if (page == 0) page = 1;
- const data = await request(url + `?ac=detail&wd=${wd}`);
- let videos = [];
- for (const vod of data.list) {
- videos.push({
- vod_id: vod.vod_id.toString(),
- vod_name: vod.vod_name.toString(),
- vod_pic: vod.vod_pic,
- vod_remarks: vod.vod_remarks,
- });
- }
- return {
- page: parseInt(data.page),
- pagecount: data.pagecount,
- total: data.total,
- list: videos,
- };
- }
- const cacheRoot = 'hls_cache';
- const hlsKeys = [];
- const hlsPlistCaches = {};
- const interrupts = {};
- const downloadTask = {};
- let currentDownloadHlsKey = '';
- function hlsCacheInsert(key, data) {
- hlsKeys.push(key);
- hlsPlistCaches[key] = data;
- if (hlsKeys.length > 5) {
- const rmKey = hlsKeys.shift();
- hlsCacheRemove(rmKey);
- }
- }
- function hlsCacheRemove(key) {
- delete hlsPlistCaches[key];
- delete hlsKeys[key];
- new JSFile(cacheRoot + '/' + key).delete();
- }
- function plistUriResolve(baseUrl, plist) {
- if (plist.variants) {
- for (const v of plist.variants) {
- if (!v.uri.startsWith('http')) {
- v.uri = relative2Absolute(baseUrl, v.uri);
- }
- }
- }
- if (plist.segments) {
- for (const s of plist.segments) {
- if (!s.uri.startsWith('http')) {
- s.uri = relative2Absolute(baseUrl, s.uri);
- }
- if (s.key && s.key.uri && !s.key.uri.startsWith('http')) {
- s.key.uri = relative2Absolute(baseUrl, s.key.uri);
- }
- }
- }
- return plist;
- }
- async function hls2Urls(url, headers) {
- let urls = [];
- let resp = {};
- let tmpUrl = url;
- while (true) {
- resp = await req(tmpUrl, {
- headers: headers,
- redirect: 0,
- });
- if (resp.headers['location']) {
- tmpUrl = resp.headers['location'];
- } else {
- break;
- }
- }
- if (resp.code == 200) {
- var hls = resp.content;
- const plist = plistUriResolve(tmpUrl, HLS.parse(hls));
- if (plist.variants) {
- for (const vari of _.sortBy(plist.variants, (v) => -1 * v.bandwidth)) {
- urls.push(`proxy_${vari.resolution.width}x${vari.resolution.height}`);
- urls.push(vari.uri);
- }
- } else {
- urls.push('proxy');
- urls.push(url);
- const hlsKey = md5X(url);
- hlsCacheInsert(hlsKey, {
- code: resp.code,
- plist: plist,
- key: hlsKey,
- headers: resp.headers,
- });
- }
- }
- return urls;
- }
- async function hlsCache(url, headers) {
- const hlsKey = md5X(url);
- if (hlsPlistCaches[hlsKey]) {
- return hlsPlistCaches[hlsKey];
- }
- let resp = {};
- let tmpUrl = url;
- while (true) {
- resp = await req(tmpUrl, {
- headers: headers,
- redirect: 0,
- });
- if (resp.headers['location']) {
- tmpUrl = resp.headers['location'];
- } else {
- break;
- }
- }
- if (resp.code == 200) {
- var hls = resp.content;
- const plist = plistUriResolve(tmpUrl, HLS.parse(hls));
- hlsCacheInsert(hlsKey, {
- code: resp.code,
- plist: plist,
- key: hlsKey,
- headers: resp.headers,
- });
- return hlsPlistCaches[hlsKey];
- }
- return {};
- }
- async function tsCache(hlsKey, segmentIndex, headers) {
- if (!hlsPlistCaches[hlsKey]) {
- return {};
- }
- const plist = hlsPlistCaches[hlsKey].plist;
- const segments = plist.segments;
- let startFirst = !downloadTask[hlsKey];
- if (startFirst) {
- downloadTask[hlsKey] = {};
- for (const seg of segments) {
- const tk = md5X(seg.uri + seg.mediaSequenceNumber.toString());
- downloadTask[hlsKey][tk] = {
- file: cacheRoot + '/' + hlsKey + '/' + tk,
- uri: seg.uri,
- key: tk,
- index: seg.mediaSequenceNumber,
- order: seg.mediaSequenceNumber,
- state: -1,
- read: false,
- };
- }
- }
- // sort task
- for (const tk in downloadTask[hlsKey]) {
- const task = downloadTask[hlsKey][tk];
- if (task.index >= segmentIndex) {
- task.order = task.index - segmentIndex;
- } else {
- task.order = segments.length - segmentIndex + task.index;
- }
- }
- if (startFirst) {
- fixedCachePool(hlsKey, 5, headers);
- }
- const segment = segments[segmentIndex];
- const tsKey = md5X(segment.uri + segment.mediaSequenceNumber.toString());
- const task = downloadTask[hlsKey][tsKey];
- if (task.state == 1 || task.state == -1) {
- const file = new JSFile(task.file);
- if (await file.exist()) {
- task.state = 1;
- // download finish
- return {
- buffer: 3,
- code: 200,
- headers: {
- connection: 'close',
- 'content-type': 'video/mp2t',
- },
- content: file,
- };
- } else {
- // file miss?? retry
- task.state = -1;
- }
- }
- if (task.state == -1) {
- // start download
- startTsTask(hlsKey, task, headers);
- }
- // wait read dwonload
- if (task.state == 0) {
- var stream = new JSProxyStream();
- stream.head(200, {
- connection: 'close',
- 'content-type': 'video/mp2t',
- });
- let downloaded = 0;
- task.read = true;
- new Promise(async function (resolve, reject) {
- const f = new JSFile(task.file + '.dl');
- await f.open('r');
- (async function waitReadFile() {
- const s = await f.size();
- if (s > downloaded) {
- var downloadBuf = await f.read(s - downloaded, downloaded);
- await stream.write(downloadBuf);
- downloaded = s;
- }
- if (task.state == 1 || task.state < 0) {
- // finish error or done
- stream.done();
- await f.close();
- await f.delete();
- task.read = false;
- resolve();
- return;
- }
- setTimeout(waitReadFile, 5);
- })();
- });
- return {
- buffer: 3,
- content: stream,
- };
- }
- }
- async function startTsTask(hlsKey, task, headers) {
- if (task.state >= 0) return;
- if (!interrupts[hlsKey]) {
- return;
- }
- task.state = 0;
- if (await new JSFile(task.file).exist()) {
- task.state = 1;
- return;
- }
- const file = new JSFile(task.file + '.dl');
- await file.open('w');
- const resp = await req(task.uri, {
- buffer: 3,
- headers: headers,
- stream: file,
- timeout: [5000, 10000],
- });
- if (resp.error || resp.code >= 300) {
- await file.close();
- if (!task.read) {
- await file.delete();
- }
- task.state = -1;
- return;
- }
- await file.close();
- if (task.read) {
- await file.copy(task.file);
- } else {
- await file.move(task.file);
- }
- task.state = 1;
- }
- async function fixedCachePool(hlsKey, limit, headers) {
- // keep last cache task only
- if (currentDownloadHlsKey && currentDownloadHlsKey != hlsKey) {
- delete interrupts[currentDownloadHlsKey];
- }
- currentDownloadHlsKey = hlsKey;
- interrupts[hlsKey] = true;
- for (let index = 0; index < limit; index++) {
- if (!interrupts[hlsKey]) break;
- new Promise(function (resolve, reject) {
- (async function doTask() {
- if (!interrupts[hlsKey]) {
- resolve();
- return;
- }
- const tasks = _.pickBy(downloadTask[hlsKey], function (o) {
- return o.state == -1;
- });
- const task = _.minBy(Object.values(tasks), function (o) {
- return o.order;
- });
- if (!task) {
- resolve();
- return;
- }
- await startTsTask(hlsKey, task, headers);
- setTimeout(doTask, 5);
- })();
- });
- }
- }
- function relative2Absolute(base, relative) {
- var stack = base.split('/'),
- parts = relative.split('/');
- stack.pop();
- for (var i = 0; i < parts.length; i++) {
- if (parts[i] == '.') continue;
- if (parts[i] == '..') stack.pop();
- else stack.push(parts[i]);
- }
- return stack.join('/');
- }
- export function __jsEvalReturn() {
- return {
- init: init,
- home: home,
- homeVod: homeVod,
- category: category,
- detail: detail,
- play: play,
- proxy: proxy,
- search: search,
- };
- }
|