Compare commits
4 Commits
ffmpegAudi
...
ffmpegAudi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc552d49f0 | ||
|
|
a4b7edd153 | ||
|
|
746da3d0b6 | ||
|
|
0b2ceb4e8c |
@@ -1,10 +1,39 @@
|
|||||||
https://live.douyin.com/629587976874,主播: 伟利火烧
|
https://live.douyin.com/925673220466,主播: 兜有米麻糍
|
||||||
https://live.douyin.com/606957569518,主播: 李泽宇(煎饼商)
|
https://live.douyin.com/745606325880,主播: w、(摆摊日记_)
|
||||||
https://live.douyin.com/288000519466,主播: 五花肉肉
|
https://live.douyin.com/675343045974,主播: 张子沐的串串(你的电子榨菜)
|
||||||
https://live.douyin.com/69064125466,主播: 张大丽丽的丽茶努力版
|
https://live.douyin.com/673565298571,主播: 轻舟已撞大冰山(摆摊vlog)
|
||||||
https://live.douyin.com/838061972379,主播: 番茄妹妹
|
https://live.douyin.com/835571459859,主播: 海绵饱饱
|
||||||
https://live.douyin.com/562629369787,主播: 叹茶柠檬茶
|
https://live.douyin.com/152358755212,主播: 大美_红烧肉
|
||||||
https://live.douyin.com/565208465584,主播: 财圆圆
|
https://live.douyin.com/990825651731,主播: 王同学摆摊日记(蜜汁烤鸡腿)
|
||||||
https://live.douyin.com/51142028798,主播: 小夏妹妹
|
https://live.douyin.com/591442402624,主播: 76696515302
|
||||||
https://live.douyin.com/974451428277,主播: 雅如柠檬茶
|
https://live.douyin.com/967381081829,主播: 憨憨的摆摊日记
|
||||||
https://live.douyin.com/334176667239,主播: 樱桃炒饭(摆摊版)
|
https://live.douyin.com/78012762575,主播: 摆摊儿去划水
|
||||||
|
https://live.douyin.com/163813589919,主播: 不要熬夜(摆摊中)
|
||||||
|
https://live.douyin.com/498368994814,主播: 剪头发的小帆帆
|
||||||
|
https://live.douyin.com/617848099204,主播: 果妈要努力
|
||||||
|
https://live.douyin.com/821525628944,主播: 李熙雨⭐
|
||||||
|
https://live.douyin.com/993102287144,主播: 小茗早餐(薪笼记助创官)
|
||||||
|
https://live.douyin.com/701547125568,主播: 悠悠包
|
||||||
|
https://live.douyin.com/816130992220,主播: 甜昕冰冰的摆摊日记
|
||||||
|
https://live.douyin.com/876468215361,主播: 晓晓和凡凡
|
||||||
|
https://live.douyin.com/511335278313,主播: 秘书(摆摊休息版)
|
||||||
|
https://live.douyin.com/483160615952,主播: 小胡同学你好(饭团版)
|
||||||
|
https://live.douyin.com/525514386431,主播: 阿闯烤肉夹馍
|
||||||
|
https://live.douyin.com/8687122573,主播: 小彤炒粉
|
||||||
|
https://live.douyin.com/642534242822,主播: (小王_)煎饼果子
|
||||||
|
https://live.douyin.com/743565594721,主播: 汪汪汪小妞
|
||||||
|
https://live.douyin.com/249578288248,主播: 思思努力版
|
||||||
|
https://live.douyin.com/472140253414,主播: 戴盘龙
|
||||||
|
https://live.douyin.com/700846653732,主播: 三哥大锅菜_炒鸡(国基路店)官方号
|
||||||
|
https://live.douyin.com/81849868631,主播: 纯儿²¹(休息勿跑空哦)
|
||||||
|
https://live.douyin.com/690114366322,主播: 卤味鲜(东大)
|
||||||
|
https://live.douyin.com/469980190666,主播: 茜茜柠檬茶(小米茶档_)
|
||||||
|
https://live.douyin.com/388066418744,主播: 加速姐的摆摊日记
|
||||||
|
https://live.douyin.com/661694696687,主播: 小米摆摊记
|
||||||
|
https://live.douyin.com/228132412943,主播: 00后摆摊小可爱
|
||||||
|
https://live.douyin.com/876677865786,主播: 牛哄哄
|
||||||
|
https://live.douyin.com/573735790640,主播: 小董卤串(餐饮)
|
||||||
|
https://live.douyin.com/260608582882,主播: 阿伟在炒饭(90後炒饭)
|
||||||
|
https://live.douyin.com/210443559964,主播: 小小诺
|
||||||
|
https://live.douyin.com/305363702471,主播: 嘉嘉在摆摊
|
||||||
|
https://live.douyin.com/143239713951,主播: ɞ_大馋丫头的烤肠
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
[youtube]
|
[youtube]
|
||||||
# 必填:YouTube 直播推流 key(在 YouTube 工作室 → 创建 → 流式传输 获取)
|
# 必填:YouTube 直播推流 key(在 YouTube 工作室 → 创建 → 流式传输 获取)
|
||||||
key = qxvb-r47b-r5ju-6ud3-6k7z
|
; key = qxvb-r47b-r5ju-6ud3-6k7z
|
||||||
; key = x04z-564w-aks7-embw-30y4
|
key = x04z-564w-aks7-embw-30y4
|
||||||
|
|
||||||
# 可选:是否使用 RTMPS 加密推流,填 否 则用 RTMP(默认 RTMPS)
|
# 可选:是否使用 RTMPS 加密推流,填 否 则用 RTMP(默认 RTMPS)
|
||||||
; rtmps = 否
|
; rtmps = 否
|
||||||
|
|||||||
107
index.html
107
index.html
@@ -32,13 +32,10 @@ button.loading::after{content:" ⏳";animation:spin 1s linear infinite}
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>
|
<h1>
|
||||||
进程:
|
进程:
|
||||||
<select id="processSelect">
|
<select id="processSelect"></select>
|
||||||
<option value="tiktok">tiktok</option>
|
|
||||||
<option value="youtube">youtube</option>
|
|
||||||
<option value="obs">obs</option>
|
|
||||||
</select>
|
|
||||||
<span id="statusIndicator">⚪ 加载中...</span>
|
<span id="statusIndicator">⚪ 加载中...</span>
|
||||||
</h1>
|
</h1>
|
||||||
|
<p id="processScriptNote" style="font-size:0.88em;margin:-12px 0 20px;opacity:0.88;font-family:ui-monospace,monospace"></p>
|
||||||
|
|
||||||
<!-- YouTube 专属配置(仅 youtube 时显示) -->
|
<!-- YouTube 专属配置(仅 youtube 时显示) -->
|
||||||
<div id="youtubeOnlySection" style="display:none;">
|
<div id="youtubeOnlySection" style="display:none;">
|
||||||
@@ -87,10 +84,55 @@ button.loading::after{content:" ⏳";animation:spin 1s linear infinite}
|
|||||||
<pre id="pm2Log">正在加载状态与日志...</pre>
|
<pre id="pm2Log">正在加载状态与日志...</pre>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
|
/** 进程列表仅来自 GET /process_monitor(web2.py 启动时扫描项目根目录自动识别);PM2 名 = 脚本主文件名(无扩展名)。 */
|
||||||
|
let PROCESS_MONITOR = [];
|
||||||
|
|
||||||
|
function pm2NameFromScript(script) {
|
||||||
|
const base = String(script).split(/[/\\]/).pop() || '';
|
||||||
|
const i = base.lastIndexOf('.');
|
||||||
|
return i > 0 ? base.slice(0, i) : base;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isYoutubeScript(script) {
|
||||||
|
return /\.py$/i.test(script) && pm2NameFromScript(script).startsWith('youtube');
|
||||||
|
}
|
||||||
|
function isTiktokScript(script) {
|
||||||
|
return /\.py$/i.test(script) && pm2NameFromScript(script).startsWith('tiktok');
|
||||||
|
}
|
||||||
|
function isObsScript(script) {
|
||||||
|
return pm2NameFromScript(script).startsWith('obs');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadProcessMonitor() {
|
||||||
|
const res = await fetch('/process_monitor');
|
||||||
|
if (!res.ok) throw new Error(String(res.status));
|
||||||
|
const data = await res.json();
|
||||||
|
const entries = data.entries || [];
|
||||||
|
PROCESS_MONITOR = entries.map((e) => ({
|
||||||
|
script: e.script,
|
||||||
|
label: e.label || e.script,
|
||||||
|
pm2: e.pm2 || pm2NameFromScript(e.script),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEntryByPm2(name) {
|
||||||
|
return PROCESS_MONITOR.find((e) => e.pm2 === name) || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fillProcessSelect() {
|
||||||
|
processSelect.innerHTML = '';
|
||||||
|
PROCESS_MONITOR.forEach((e) => {
|
||||||
|
const opt = document.createElement('option');
|
||||||
|
opt.value = e.pm2;
|
||||||
|
opt.textContent = `${e.label}(${e.script})`;
|
||||||
|
processSelect.appendChild(opt);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const logEl = document.getElementById('pm2Log');
|
const logEl = document.getElementById('pm2Log');
|
||||||
const statusIndicator = document.getElementById('statusIndicator');
|
const statusIndicator = document.getElementById('statusIndicator');
|
||||||
const processSelect = document.getElementById('processSelect');
|
const processSelect = document.getElementById('processSelect');
|
||||||
let currentProcess = 'tiktok';
|
let currentProcess = '';
|
||||||
|
|
||||||
// 各专属区域
|
// 各专属区域
|
||||||
const youtubeOnlySection = document.getElementById('youtubeOnlySection');
|
const youtubeOnlySection = document.getElementById('youtubeOnlySection');
|
||||||
@@ -109,23 +151,32 @@ const loadUrlBtn = document.getElementById('loadUrlBtn');
|
|||||||
const saveUrlBtn = document.getElementById('saveUrlBtn');
|
const saveUrlBtn = document.getElementById('saveUrlBtn');
|
||||||
const urlStatus = document.getElementById('urlStatus');
|
const urlStatus = document.getElementById('urlStatus');
|
||||||
|
|
||||||
|
function refreshProcessScriptNote() {
|
||||||
|
const el = document.getElementById('processScriptNote');
|
||||||
|
const ent = getEntryByPm2(currentProcess);
|
||||||
|
el.textContent = ent ? `脚本: ${ent.script} · PM2 名: ${ent.pm2}` : '';
|
||||||
|
}
|
||||||
|
|
||||||
processSelect.addEventListener('change', () => {
|
processSelect.addEventListener('change', () => {
|
||||||
currentProcess = processSelect.value;
|
currentProcess = processSelect.value;
|
||||||
|
refreshProcessScriptNote();
|
||||||
fetchStatus();
|
fetchStatus();
|
||||||
updateSectionsVisibility();
|
updateSectionsVisibility();
|
||||||
});
|
});
|
||||||
|
|
||||||
function updateSectionsVisibility() {
|
function updateSectionsVisibility() {
|
||||||
youtubeOnlySection.style.display = currentProcess === 'youtube' ? 'block' : 'none';
|
const ent = getEntryByPm2(currentProcess);
|
||||||
urlConfigSection.style.display = (currentProcess === 'youtube' || currentProcess === 'tiktok') ? 'block' : 'none';
|
const s = ent ? ent.script : '';
|
||||||
obsSection.style.display = currentProcess === 'obs' ? 'block' : 'none';
|
const isYoutube = ent && isYoutubeScript(s);
|
||||||
|
const isTiktok = ent && isTiktokScript(s);
|
||||||
|
const isObs = ent && isObsScript(s);
|
||||||
|
|
||||||
if (currentProcess === 'youtube') {
|
youtubeOnlySection.style.display = isYoutube ? 'block' : 'none';
|
||||||
loadYoutubeConfig();
|
urlConfigSection.style.display = (isYoutube || isTiktok) ? 'block' : 'none';
|
||||||
}
|
obsSection.style.display = isObs ? 'block' : 'none';
|
||||||
if (currentProcess === 'youtube' || currentProcess === 'tiktok') {
|
|
||||||
loadUrlConfig();
|
if (isYoutube) loadYoutubeConfig();
|
||||||
}
|
if (isYoutube || isTiktok) loadUrlConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
// textarea 高度自适应
|
// textarea 高度自适应
|
||||||
@@ -215,7 +266,9 @@ async function pm2Action(action){
|
|||||||
updateStatus(data.process_status);
|
updateStatus(data.process_status);
|
||||||
let logText = `【PM2 全状态表格】(${new Date().toLocaleTimeString()})\n${data.raw_status}\n\n`;
|
let logText = `【PM2 全状态表格】(${new Date().toLocaleTimeString()})\n${data.raw_status}\n\n`;
|
||||||
if(data.process_status === 'not_found'){
|
if(data.process_status === 'not_found'){
|
||||||
logText += `【提示】进程 "${currentProcess}" 未注册到 PM2(可能被 pm2 delete 删除)\n`;
|
const ent = getEntryByPm2(currentProcess);
|
||||||
|
const hint = ent ? `${ent.script}(PM2 名: ${ent.pm2})` : currentProcess;
|
||||||
|
logText += `【提示】未在 PM2 中找到: ${hint}\n`;
|
||||||
}else{
|
}else{
|
||||||
logText += `【实时输出日志】`;
|
logText += `【实时输出日志】`;
|
||||||
if(data.recent_log.includes('不存在') || data.recent_log.includes('无日志路径')){
|
if(data.recent_log.includes('不存在') || data.recent_log.includes('无日志路径')){
|
||||||
@@ -233,7 +286,9 @@ async function pm2Action(action){
|
|||||||
logEl.textContent = logText;
|
logEl.textContent = logText;
|
||||||
scrollLog();
|
scrollLog();
|
||||||
}else{
|
}else{
|
||||||
logEl.textContent = `操作: ${action} ${currentProcess}\n${data.output || '成功'}\n\n正在刷新状态...`;
|
const ent = getEntryByPm2(currentProcess);
|
||||||
|
const who = ent ? `${ent.script} (${ent.pm2})` : currentProcess;
|
||||||
|
logEl.textContent = `操作: ${action} → ${who}\n${data.output || '成功'}\n\n正在刷新状态...`;
|
||||||
scrollLog();
|
scrollLog();
|
||||||
setTimeout(fetchStatus, 1500);
|
setTimeout(fetchStatus, 1500);
|
||||||
}
|
}
|
||||||
@@ -256,10 +311,26 @@ document.getElementById('pm2Buttons').addEventListener('click', e=>{
|
|||||||
if(action && action !== 'status') pm2Action(action);
|
if(action && action !== 'status') pm2Action(action);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 自动刷新 + 初始
|
(async function boot() {
|
||||||
|
try {
|
||||||
|
await loadProcessMonitor();
|
||||||
|
} catch (e) {
|
||||||
|
logEl.textContent = '无法加载 /process_monitor(请用本服务打开的页面访问,或检查 web2 是否已启动)\n' + (e && e.message ? e.message : '');
|
||||||
|
statusIndicator.textContent = '❌ 未连接';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!PROCESS_MONITOR.length) {
|
||||||
|
logEl.textContent = '未发现任何入口脚本(项目根应有 tiktok*.py / youtube*.py / obs*.sh,且 Web 为当前 web*.py)';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fillProcessSelect();
|
||||||
|
currentProcess = PROCESS_MONITOR[0].pm2;
|
||||||
|
processSelect.value = currentProcess;
|
||||||
|
refreshProcessScriptNote();
|
||||||
setInterval(fetchStatus, 1000);
|
setInterval(fetchStatus, 1000);
|
||||||
fetchStatus();
|
fetchStatus();
|
||||||
updateSectionsVisibility();
|
updateSectionsVisibility();
|
||||||
|
})();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
14
index.html.1
14
index.html.1
@@ -1,14 +0,0 @@
|
|||||||
<!doctype html><html itemscope="" itemtype="http://schema.org/WebPage" lang="en"><head><meta content="Search the world's information, including webpages, images, videos and more. Google has many special features to help you find exactly what you're looking for." name="description"><meta content="noodp, " name="robots"><meta content="text/html; charset=UTF-8" http-equiv="Content-Type"><meta content="/images/branding/googleg/1x/googleg_standard_color_128dp.png" itemprop="image"><title>Google</title><script nonce="mkn-uSdWx2SoIvEJXJWf7Q">(function(){var _g={kEI:'gbObaeXAG-Gy5NoPmeGiyAg',kEXPI:'0,1304203,2935842,14111,64702,6398,354503,5532962,16,36811390,25228681,123988,28395,11943,53220,39775,61250,14067,23259,37800,28333,79315,7707,7,33385,4719,24374,1,2884,3086,16757,46340,9920,10754,11886,6436,4952,4791,2646,6292,12404,5550,14505,1383,1,2,1515,3355,196,5814,2,4205,1,16100,2,873,2,22,2208,5108,3008,24,771,4074,4,5393,18686,8466,5337,310,535,845,1201,2,11,2,1560,3,5189,1,13,738,1009,2,1746,4,4878,5773,4542,4,2243,240,297,1,1117,389,4,2647,5,2698,2870,298,9253,1275,7,1784,1050,2,1914,5,10456,2,5724,9,602,59,36,1554,4,2570,3341,932,3066,1,2,1402,3,1117,242,2,2356,2576,49,1,8,216,441,158,2,1645,191,4,1928,4,1634,544,4,1568,1032,1,1399,187,259,1846,5,259,5,197,1921,445,19,1421,119,4,2364,2363,5,39,633,1937,4,57,4,3784,4,76,1970,4,265,106,5,3,971,4,966,5,1125,1,2,2,815,3,2932,4,32,4,130,717,1856,4,40,4,2110,4,134,304,323,1,576,1776,1864,42,5,2,479,704,4,14,3,1,449,4,730,5,75,4,1918,1,21,4,465,429,537,1563,1,1090,2,265,702,4905,1788,14,507,823,747,4,331,4,959,452,57,258,615,80,7,2245,4,281,264,341,221,1,2976,508,339,3430,503,3,1412,1354,4,12,33,2093,4,945,16,4,612,1,20998529,1717,7635,5,2992,4,2960,3,8491,2,1562,3,2687,3,9181,536,2,2461,1194,3,746,6,918,231,3,1369,1494,2715,1,6494629,4214,2,3639,565,745,38,517,541,84,108428,25,36,10,3516226,209655,562,143,12493584,256605,48968,4054456,476,441,42,5,116,590,5,1023,1671,844,1,148,3,6,163,101,343,4,65,388,2,707,260,44,4,152,4,488,62,219,3,2,2,2,550,4,885,1050,2,50,641,5,429,6,2800,5,138,1,863,130,45,2,11,2,16,361,218,1148,1,2,642,214,1485,417,712,2695,1043,281,5,330,1255,292,114,289,54,161,5,142,4,587,412,322,525,1,361,503,213,249,99,472,165,7,51,90,174,436,472,3,289,379,458,583,331,601,587,556,100,949,821,118,4,177,37,7,12,3,6,25,1006,3,2,2,2,67,134,727,4,283,321,70,212,125,4,4,3,2,1,2,3,2,2,1,2,2,2,3,1,3,2,1,3,2,1,3,1,2,3,1,2,2,2,3,2,1,3,1,2,3,2,2,2,2,1,3,1,3,2,1,2,10,2,2,3,2,2,10,1,2,3,1,2,10,2,3,2,1,3,9,2,3,1,3,2,1,2,2,2,3,1,2,3,2,2,1,3,1,3,2,1,2,2,2,2,2,3,2,2,1,2,3,2,1,2,2,2,3,1,3,1,3,1,2,2,10,2,3,1,3,1,11,2,2,1,2,2,10,2,3,2,2,1,183,94,60,120,149,39,317,3,2,2,2,744,296,177,5,22,414,377,24,216,1579,8,293,133,136,134,83,118,2,3,2,2,2,956,1,2,785,3,2,2,2,348,295,4,106,229,180,5,62,2,878,573,260,185,936,640,1027,638,713,94,18,3,2,2,2,4,308,593,3,2,2,2,720,519,6,1,4357,427,3,1,370,435,364,71,61,442,901,4',kBL:'eaQ3',kOPI:89978449};(function(){var a;((a=window.google)==null?0:a.stvsc)?google.kEI=_g.kEI:window.google=_g;}).call(this);})();(function(){google.sn='webhp';google.kHL='en';google.rdn=true;})();(function(){
|
|
||||||
var g=this||self;function k(){return window.google&&window.google.kOPI||null};var l,m=[];function n(a){for(var b;a&&(!a.getAttribute||!(b=a.getAttribute("eid")));)a=a.parentNode;return b||l}function p(a){for(var b=null;a&&(!a.getAttribute||!(b=a.getAttribute("leid")));)a=a.parentNode;return b}function q(a){/^http:/i.test(a)&&window.location.protocol==="https:"&&(google.ml&&google.ml(Error("a"),!1,{src:a,glmm:1}),a="");return a}
|
|
||||||
function r(a,b,d,c,h){var e="";b.search("&ei=")===-1&&(e="&ei="+n(c),b.search("&lei=")===-1&&(c=p(c))&&(e+="&lei="+c));var f=b.search("&cshid=")===-1&&a!=="slh";c="&zx="+Date.now().toString();g._cshid&&f&&(c+="&cshid="+g._cshid);(d=d())&&(c+="&opi="+d);return"/"+(h||"gen_204")+"?atyp=i&ct="+String(a)+"&cad="+(b+e+c)};l=google.kEI;google.getEI=n;google.getLEI=p;google.ml=function(){return null};google.log=function(a,b,d,c,h,e){e=e===void 0?k:e;d||(d=r(a,b,e,c,h));if(d=q(d)){a=new Image;var f=m.length;m[f]=a;a.onerror=a.onload=a.onabort=function(){delete m[f]};a.src=d}};google.logUrl=function(a,b){b=b===void 0?k:b;return r("",a,b)};}).call(this);(function(){google.y={};google.sy={};function e(a,b,d){if(a)var c=a.id;else{do c=Math.random();while(d[c])}d[c]=[a,b]}var f;(f=google).x||(f.x=function(a,b){e(a,b,google.y)});var g;(g=google).sx||(g.sx=function(a,b){e(a,b,google.sy)});google.bx=!1;var h;(h=google).lx||(h.lx=function(){});var k=[],l;(l=google).fce||(l.fce=function(a,b,d,c){k.push([a,b,d,c])});google.qce=k;google.adl=[];}).call(this);google.f={};(function(){
|
|
||||||
document.documentElement.addEventListener("submit",function(b){var a;if(a=b.target){var c=a.getAttribute("data-submitfalse");a=c==="1"||c==="q"&&!a.elements.q.value?!0:!1}else a=!1;a&&(b.preventDefault(),b.stopPropagation())},!0);document.documentElement.addEventListener("click",function(b){var a;a:{for(a=b.target;a&&a!==document.documentElement;a=a.parentElement)if(a.tagName==="A"){a=a.getAttribute("data-nohref")==="1";break a}a=!1}a&&b.preventDefault()},!0);}).call(this);</script><style>#gbar,#guser{font-size:13px;padding-top:1px !important;}#gbar{height:22px}#guser{padding-bottom:7px !important;text-align:right}.gbh,.gbd{border-top:1px solid #c9d7f1;font-size:1px}.gbh{height:0;position:absolute;top:24px;width:100%}@media all{.gb1{height:22px;margin-right:.5em;vertical-align:top}#gbar{float:left}}a.gb1,a.gb4{text-decoration:underline !important}a.gb1,a.gb4{color:#00c !important}.gbi .gb4{color:#dd8e27 !important}.gbf .gb4{color:#900 !important}
|
|
||||||
</style><style>body,td,a,p,.h{font-family:sans-serif}body{margin:0;overflow-y:scroll}#gog{padding:3px 8px 0}td{line-height:.8em}.gac_m td{line-height:17px}form{margin-bottom:20px}.h{color:#1967d2}em{font-weight:bold;font-style:normal}.lst{height:25px;width:496px}.gsfi,.lst{font:18px sans-serif}.gsfs{font:17px sans-serif}.ds{display:inline-box;display:inline-block;margin:3px 0 4px;margin-left:4px}input{font-family:inherit}body{background:#fff;color:#1f1f1f}a{color:#681da8;text-decoration:none}a:hover,a:active{text-decoration:underline}.fl a{color:#1967d2}a:visited{color:#681da8}.sblc{padding-top:5px}.sblc a{display:block;margin:2px 0;margin-left:13px;font-size:11px}.lsbb{background:#ecedee;border:solid 1px;border-color:#d2d2d2 #70757a #70757a #d2d2d2;height:30px}.lsbb{display:block}#WqQANb a{display:inline-block;margin:0 12px}.lsb{background:url(/images/nav_logo229.png) 0 -261px repeat-x;color:#1f1f1f;border:none;cursor:pointer;height:30px;margin:0;outline:0;font:15px sans-serif;vertical-align:top}.lsb:active{background:#dadce0}.lst:focus{outline:none}</style><script nonce="mkn-uSdWx2SoIvEJXJWf7Q">(function(){window.google.erd={jsr:1,bv:2383,de:true,dpf:'UAON6G49novXd1LJI--iGd6VfzOOuK54njDRAqwNAgA'};
|
|
||||||
var g=this||self;var k,l=(k=g.mei)!=null?k:1,m,p=(m=g.diel)!=null?m:0,q,r=(q=g.sdo)!=null?q:!0,t=0,u,w=google.erd,x=w.jsr;google.ml=function(a,b,d,n,e){e=e===void 0?2:e;b&&(u=a&&a.message);d===void 0&&(d={});d.cad="ple_"+google.ple+".aple_"+google.aple;if(google.dl)return google.dl(a,e,d,!0),null;b=d;if(x<0){window.console&&console.error(a,b);if(x===-2)throw a;b=!1}else b=!a||!a.message||a.message==="Error loading script"||t>=l&&!n?!1:!0;if(!b)return null;t++;d=d||{};b=encodeURIComponent;var c="/gen_204?atyp=i&ei="+b(google.kEI);google.kEXPI&&(c+="&jexpid="+b(google.kEXPI));c+="&srcpg="+b(google.sn)+"&jsr="+b(w.jsr)+
|
|
||||||
"&bver="+b(w.bv);w.dpf&&(c+="&dpf="+b(w.dpf));var f=a.lineNumber;f!==void 0&&(c+="&line="+f);var h=a.fileName;h&&(h.indexOf("-extension:/")>0&&(e=3),c+="&script="+b(h),f&&h===window.location.href&&(f=document.documentElement.outerHTML.split("\n")[f],c+="&cad="+b(f?f.substring(0,300):"No script found.")));google.ple&&google.ple===1&&(e=2);c+="&jsel="+e;for(var v in d)c+="&",c+=b(v),c+="=",c+=b(d[v]);c=c+"&emsg="+b(a.name+": "+a.message);c=c+"&jsst="+b(a.stack||"N/A");c.length>=12288&&(c=c.substring(0,12288));a=c;n||google.log(0,"",a);return a};window.onerror=function(a,b,d,n,e){u!==a&&(a=e instanceof Error?e:Error(a),d===void 0||"lineNumber"in a||(a.lineNumber=d),b===void 0||"fileName"in a||(a.fileName=b),google.ml(a,!1,void 0,!1,a.name==="SyntaxError"||a.message.substring(0,11)==="SyntaxError"||a.message.indexOf("Script error")!==-1?3:p));u=null;r&&t>=l&&(window.onerror=null)};})();</script></head><body bgcolor="#fff"><script nonce="mkn-uSdWx2SoIvEJXJWf7Q">(function(){var src='/images/nav_logo229.png';var iesg=false;document.body.onload = function(){window.n && window.n();if (document.images){new Image().src=src;}
|
|
||||||
if (!iesg){document.f&&document.f.q.focus();document.gbqf&&document.gbqf.q.focus();}
|
|
||||||
}
|
|
||||||
})();</script><div id="mngb"><div id=gbar><nobr><b class=gb1>Search</b> <a class=gb1 href="https://www.google.com/imghp?hl=en&tab=wi">Images</a> <a class=gb1 href="http://maps.google.com/maps?hl=en&tab=wl">Maps</a> <a class=gb1 href="https://play.google.com/?hl=en&tab=w8">Play</a> <a class=gb1 href="https://www.youtube.com/?tab=w1">YouTube</a> <a class=gb1 href="https://news.google.com/?tab=wn">News</a> <a class=gb1 href="https://mail.google.com/mail/?tab=wm">Gmail</a> <a class=gb1 href="https://drive.google.com/?tab=wo">Drive</a> <a class=gb1 style="text-decoration:none" href="https://www.google.com/intl/en/about/products?tab=wh"><u>More</u> »</a></nobr></div><div id=guser width=100%><nobr><span id=gbn class=gbi></span><span id=gbf class=gbf></span><span id=gbe></span><a href="http://www.google.com/history/optout?hl=en" class=gb4>Web History</a> | <a href="/preferences?hl=en" class=gb4>Settings</a> | <a target=_top id=gb_70 href="https://accounts.google.com/ServiceLogin?hl=en&passive=true&continue=http://www.google.com/&ec=GAZAAQ" class=gb4>Sign in</a></nobr></div><div class=gbh style=left:0></div><div class=gbh style=right:0></div></div><center><br clear="all" id="lgpd"><div><img alt="Google" height="92" src="/images/branding/googlelogo/1x/googlelogo_white_background_color_272x92dp.png" style="padding:28px 0 14px" width="272" id="hplogo"><br><br></div><form action="/search" name="f"><table cellpadding="0" cellspacing="0"><tr valign="top"><td width="25%"> </td><td align="center" nowrap=""><input name="ie" value="ISO-8859-1" type="hidden"><input value="en" name="hl" type="hidden"><input name="source" type="hidden" value="hp"><input name="biw" type="hidden"><input name="bih" type="hidden"><div class="ds" style="height:32px;margin:4px 0"><input class="lst" style="margin:0;padding:5px 8px 0 6px;vertical-align:top;color:#1f1f1f" autocomplete="off" value="" title="Google Search" maxlength="2048" name="q" size="57"></div><br style="line-height:0"><span class="ds"><span class="lsbb"><input class="lsb" value="Google Search" name="btnG" type="submit"></span></span><span class="ds"><span class="lsbb"><input class="lsb" id="tsuid_gbObaeXAG-Gy5NoPmeGiyAg_1" value="I'm Feeling Lucky" name="btnI" type="submit"><script nonce="mkn-uSdWx2SoIvEJXJWf7Q">(function(){var id='tsuid_gbObaeXAG-Gy5NoPmeGiyAg_1';document.getElementById(id).onclick = function(){if (this.form.q.value){this.checked = 1;if (this.form.iflsig)this.form.iflsig.disabled = false;}
|
|
||||||
else top.location='/doodles/';};})();</script><input value="AFdpzrgAAAAAaZvBkZ6x6tP9yqY4FfFqM-NYRs9cR3zt" name="iflsig" type="hidden"></span></span></td><td class="fl sblc" align="left" nowrap="" width="25%"><a href="/advanced_search?hl=en&authuser=0">Advanced search</a></td></tr></table><input id="gbv" name="gbv" type="hidden" value="1"><script nonce="mkn-uSdWx2SoIvEJXJWf7Q">(function(){var a,b="1";if(document&&document.getElementById)if(typeof XMLHttpRequest!="undefined")b="2";else if(typeof ActiveXObject!="undefined"){var c,d,e=["MSXML2.XMLHTTP.6.0","MSXML2.XMLHTTP.3.0","MSXML2.XMLHTTP","Microsoft.XMLHTTP"];for(c=0;d=e[c++];)try{new ActiveXObject(d),b="2"}catch(h){}}a=b;if(a=="2"&&location.search.indexOf("&gbv=2")==-1){var f=google.gbvu,g=document.getElementById("gbv");g&&(g.value=a);f&&window.setTimeout(function(){location.href=f},0)};}).call(this);</script></form><div style="font-size:83%;min-height:3.5em"><br></div><span id="footer"><div style="font-size:10pt"><div style="margin:19px auto;text-align:center" id="WqQANb"><a href="/intl/en/ads/">Advertising</a><a href="/services/">Business Solutions</a><a href="/intl/en/about.html">About Google</a></div></div><p style="font-size:8pt;color:#636363">© 2026 - <a href="/intl/en/policies/privacy/">Privacy</a> - <a href="/intl/en/policies/terms/">Terms</a></p></span></center><script nonce="mkn-uSdWx2SoIvEJXJWf7Q">(function(){window.google.cdo={height:757,width:1440};(function(){var a=window.innerWidth,b=window.innerHeight;if(!a||!b){var c=window.document,d=c.compatMode=="CSS1Compat"?c.documentElement:c.body;a=d.clientWidth;b=d.clientHeight}if(a&&b&&(a!=google.cdo.width||b!=google.cdo.height)){var e=google,f=e.log,g="/client_204?&atyp=i&biw="+a+"&bih="+b+"&ei="+google.kEI,h="",k=window.google&&window.google.kOPI||null;k&&(h+="&opi="+k);f.call(e,"","",g+h)};}).call(this);})();(function(){google.xjs={basecomb:'/xjs/_/js/k\x3dxjs.hp.en.p4I6t01-OYQ.es5.O/ck\x3dxjs.hp.KF58xD7gYsk.L.X.O/am\x3dAAIAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAQAAAAICAQAAAAAAAAAAAAAAAAAABAAAAAAAABAMAAAAAEgBAAAAAAAEAAAAgAAAAAgAAAQAAIAAADEHQEAAAAAgABYBAAAAAAAAB4C/d\x3d1/ed\x3d1/dg\x3d0/ujg\x3d1/rs\x3dACT90oHYvixg5Dv0yQj-SB1JU6LIcj3yYQ',basecss:'/xjs/_/ss/k\x3dxjs.hp.KF58xD7gYsk.L.X.O/am\x3dAAIAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAQAAAAIAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEgBAAAAAAAEAAAAgAAAAAgAAAQAAI/rs\x3dACT90oFMtqhjIKGT4rLyv9oL3Z6ncHStwQ',basejs:'/xjs/_/js/k\x3dxjs.hp.en.p4I6t01-OYQ.es5.O/am\x3dAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAABAAAAAAAABAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADEHQEAAAAAgABYBAAAAAAAAB4C/dg\x3d0/rs\x3dACT90oE3jOtIqglc8PGrnS3gM3dxeZxTQA',excm:[]};})();(function(){var u='/xjs/_/js/k\x3dxjs.hp.en.p4I6t01-OYQ.es5.O/am\x3dAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAABAAAAAAAABAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADEHQEAAAAAgABYBAAAAAAAAB4C/d\x3d1/ed\x3d1/dg\x3d4/rs\x3dACT90oE3jOtIqglc8PGrnS3gM3dxeZxTQA/m\x3dsb_he,d';var st=1;var amd=1000;var mmd=0;var pop=true;var povp=false;var raf=false;var lfpp=false;var ecb=false;var cst=false;var pxp=false;
|
|
||||||
var f=this||self;function g(){var a=google.ia&&google.ia.r.B2Jtyd;return a&&[1,5,6].indexOf(a.m)>=0&&a.cbfd&&a.cbvi?a:void 0};function h(a){var b=document.createElement("link");b.as="script";b.href=a;b.rel="preload";document.body.appendChild(b)}function k(){var a=[l];google.dp||(a.forEach(h),google.dp=!0)};google.ps=google.ps||[];function m(a){var b=l,c=function(){};google.lx=google.stvsc?c:function(){n(b,a&&raf,a&&lfpp);google.lx=c};google.bx||google.lx()}function p(a,b){b&&(a.src=b);var c=a.onload;a.onload=function(d){c&&c(d);google.ps=google.ps.filter(function(e){return a.src!==e})};google.ps.push(a.src);document.body.appendChild(a)}function q(a,b,c,d){c&&(a.fetchPriority="low");b?requestAnimationFrame(function(){p(a,d)}):p(a,d)}google.as=p;function n(a,b,c){google.tick&&google.tick("load","xjsls");var d=document.createElement("script");d.onerror=function(){google.ple=1};d.onload=function(){google.ple=0};google.xjsus=void 0;q(d,b,c,a);google.aple=-1;google.dp=!0};function r(){for(var a=document.getElementsByTagName("img"),b=0,c=a.length;b<c;b++){var d=a[b],e;if(e=d.hasAttribute("data-lzy_")&&Number(d.getAttribute("data-atf"))&1)e=d.getAttribute("jscontroller"),e=!((e==="UBXHI"||e==="R3fhkb"||e==="TSZEqd")&&d.hasAttribute("data-src"));if(e)return!0}return!1};var l,t,w,x,y,z,A,B,C,D,E="";function F(){l=pxp&&google.xjsup||u;var a=l.match("/cb=(loaded_h_\\d+)");a&&a[1]&&(E=a[1]);google.xjsu=l;f._F_jsUrl=l;z=function(c){m(c)};t=!1;w=(st===1||st===3)&&!!google.caft&&!r();x=g();y=!E&&(st===2||st===3)&&!!x&&!r();if(E){var b=(st===2||st===3)&&!!x;f[E]=function(c){var d=!1,e=function(){d||(d=!0,cst?setTimeout(function(){return void c.call(window,window._)},0):c.call(window,window._))};b&&google.ia.adls?(x.cbvi.push(function(){delete google.ia.adls}),x.cbvi.push(e),setTimeout(e,mmd)):e()}}A=
|
|
||||||
pop;B=povp;C=A&&document.prerendering||B&&document.hidden;D=B?"visibilitychange":"prerenderingchange"}function G(a){t||w||y||C||(z(a),t=!0)}
|
|
||||||
setTimeout(function(){google.tick&&google.tick("load","xjspls");F();if(w||y||C){if(w){var a=function(){w=!1;G()};google.caft(a);setTimeout(a,amd)}y&&(a=function(){y=!1;G()},x.cbvi.push(a),setTimeout(a,mmd));if(C){var b=function(){(B?document.hidden:document.prerendering)||(C=!1,G(!B),document.removeEventListener(D,b))};document.addEventListener(D,b,{passive:!0})}t||k()}else z()},0);})();window._ = window._ || {};window._DumpException = _._DumpException = function(e){throw e;};window._s = window._s || {};_s._DumpException = _._DumpException;window._qs = window._qs || {};_qs._DumpException = _._DumpException;window.loaded_h_0 = function(cb){cb.call(window,window._);};(function(){var t=[512,0,2048,0,0,0,4194304,33554432,268437004,0,8192,0,4096,4259020,137036800,134234113,548405252,4196417,8192,134217728,1048576,8,1170496,0,72908928,0,568328192];window._F_toggles = window._xjs_toggles = t;})();window._F_installCss = window._F_installCss || function(css){};(function(){var pmc='{\x22d\x22:{},\x22sb_he\x22:{\x22client\x22:\x22heirloom-hp\x22,\x22dh\x22:true,\x22ds\x22:\x22\x22,\x22host\x22:\x22google.com\x22,\x22jsonp\x22:true,\x22msgs\x22:{\x22cibl\x22:\x22Clear Search\x22,\x22dym\x22:\x22Did you mean:\x22,\x22lcky\x22:\x22I\\u0026#39;m Feeling Lucky\x22,\x22lml\x22:\x22Learn more\x22,\x22psrc\x22:\x22This search was removed from your \\u003Ca href\x3d\\\x22/history\\\x22\\u003EWeb History\\u003C/a\\u003E\x22,\x22psrl\x22:\x22Remove\x22,\x22sbit\x22:\x22Search by image\x22,\x22srch\x22:\x22Google Search\x22},\x22ovr\x22:{},\x22pq\x22:\x22\x22,\x22rfs\x22:[],\x22stok\x22:\x22VgWHNe9WWnZFTDWuPRUlX6IPsTw\x22}}';google.pmc=JSON.parse(pmc);})();</script></body></html>
|
|
||||||
215
indexOrigin.html
215
indexOrigin.html
@@ -1,215 +0,0 @@
|
|||||||
<!--
|
|
||||||
Project: DouyinLiveRecorder
|
|
||||||
Author: Hmily
|
|
||||||
Build: 2023.08.14 - 20:24:05
|
|
||||||
GitHub Project URL: https://github.com/ihmily/DouyinLiveRecorder
|
|
||||||
-->
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<meta name="referrer" content="never">
|
|
||||||
<title>M3U8 视频播放器</title>
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" rel="stylesheet">
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest/dist/hls.min.js"></script>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/flv.js@1.6.2/dist/flv.min.js"></script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: 'Roboto', Arial, sans-serif;
|
|
||||||
background-color: #1a237e;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
height: 100vh;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
color: #ffffff;
|
|
||||||
background-image: linear-gradient(120deg, #1a237e 0%, #283593 50%, #4a148c 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.container {
|
|
||||||
max-width: 640px;
|
|
||||||
width: 80%;
|
|
||||||
padding: 20px;
|
|
||||||
background-color: #ffffff;
|
|
||||||
border-radius: 10px;
|
|
||||||
box-shadow: 0px 0px 5px 0px rgba(0,0,0,0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
#videoPlayer {
|
|
||||||
width: 100%;
|
|
||||||
height: 0;
|
|
||||||
padding-bottom: 56.25%;
|
|
||||||
position: relative;
|
|
||||||
background-color: #000;
|
|
||||||
border-radius: 5px;
|
|
||||||
box-shadow: 0px 0px 10px 0px rgba(0,0,0,0.15);
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
video {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#videoUrlInput{
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
margin: 10px 0;
|
|
||||||
padding: 8px;
|
|
||||||
border-radius: 5px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
#playButton {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
padding: 10px;
|
|
||||||
background-color: #283593;
|
|
||||||
color: white;
|
|
||||||
font-weight: bold;
|
|
||||||
border-radius: 5px;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background-color 0.3s;
|
|
||||||
margin: 0 0 10px 0;
|
|
||||||
box-shadow: 0px 2px 4px 0px rgba(0,0,0,0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
#playButton:hover {
|
|
||||||
background-color: #1a237e;
|
|
||||||
}
|
|
||||||
|
|
||||||
.description {
|
|
||||||
margin-top: 20px;
|
|
||||||
line-height: 1.4;
|
|
||||||
font-size: 14px;
|
|
||||||
text-align: left;
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
padding: 15px;
|
|
||||||
border-radius: 5px;
|
|
||||||
|
|
||||||
box-shadow: 0px 0px 5px 0px rgba(0,0,0,0.15);
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.footer {
|
|
||||||
margin-top: 30px;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 14px;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
p{
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.no_style {
|
|
||||||
color: inherit;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
@media screen and (max-width: 768px) {
|
|
||||||
.container {
|
|
||||||
width: 90%;
|
|
||||||
border-radius: 0;
|
|
||||||
box-shadow: none;
|
|
||||||
margin-top:30px;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
overflow-y: scroll;
|
|
||||||
}
|
|
||||||
#videoUrlInput{
|
|
||||||
|
|
||||||
margin-top: 30px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<input type="text" id="videoUrlInput" placeholder="请输入 M3U8或者FLV 视频链接">
|
|
||||||
<button id="playButton">播放视频</button>
|
|
||||||
<div id="videoPlayer">
|
|
||||||
<video controls></video>
|
|
||||||
</div>
|
|
||||||
<div class="description">
|
|
||||||
<p><strong>说明</strong><p>
|
|
||||||
<p>M3U8文件格式</p>
|
|
||||||
<p>M3U8文件是采用UTF-8编码格式的M3U文件。M3U文件本身是一个纯文本索引文件,其核心功能是记录多媒体文件链接。当用户打开此类文件时,播放软件会根据索引查找相应的音视频文件网络地址,然后进行在线播放。</p>
|
|
||||||
<p>M3U最初设计用于播放音频文件,例如MP3。但随着时间推移,更多的播放器和软件开始使用M3U来播放视频文件列表,同时也支持在线流媒体音频源的指定。目前,许多播放器和软件都兼容M3U文件格式。</p>
|
|
||||||
<p>FLV文件格式(Flash Video Format)是Adobe公司开发的一种专门用于网页视频播放的文件格式。FLV格式的视频文件通常用于播放短视频和在线流媒体,可以嵌入到网页中供用户观看。FLV视频通常由Adobe Flash Player播放器播放,而其他第三方播放器也支持此格式。</p>
|
|
||||||
</div>
|
|
||||||
<div class="footer">
|
|
||||||
<p>© 2023 <a href='https://github.com/ihmily/DouyinLiveRecorder' class="no_style" target="_blank">Hmily</a>. All rights reserved.</p>
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
function httpToHttps(url) {
|
|
||||||
if (url.startsWith("http://")) {
|
|
||||||
return url.replace("http://", "https://");
|
|
||||||
}
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
function playVideo() {
|
|
||||||
let videoUrl = document.getElementById('videoUrlInput').value;
|
|
||||||
const video = document.querySelector('#videoPlayer video');
|
|
||||||
const description = document.querySelector('.description');
|
|
||||||
if (videoUrl == ''){
|
|
||||||
alert('请输入视频链接');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
videoUrl = httpToHttps(videoUrl);
|
|
||||||
if (videoUrl.includes('.m3u8')) {
|
|
||||||
videoPlayer.style.display = 'block';
|
|
||||||
description.style.display = 'none';
|
|
||||||
if (Hls.isSupported()) {
|
|
||||||
const hls = new Hls();
|
|
||||||
hls.attachMedia(video);
|
|
||||||
hls.on(Hls.Events.MEDIA_ATTACHED, () => {
|
|
||||||
hls.loadSource(videoUrl);
|
|
||||||
});
|
|
||||||
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
|
|
||||||
video.src = videoUrl;
|
|
||||||
} else {
|
|
||||||
alert('M3U8 格式不受您的浏览器支持。');
|
|
||||||
console.error('M3U8 格式不受您的浏览器支持。');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else if (videoUrl.includes('.flv')) {
|
|
||||||
if (flvjs.isSupported()) {
|
|
||||||
const flvPlayer = flvjs.createPlayer({
|
|
||||||
type: 'flv',
|
|
||||||
url: videoUrl
|
|
||||||
});
|
|
||||||
flvPlayer.attachMediaElement(video);
|
|
||||||
flvPlayer.load();
|
|
||||||
flvPlayer.play();
|
|
||||||
} else {
|
|
||||||
alert('FLV 格式不受您的浏览器支持。');
|
|
||||||
console.error('FLV 格式不受您的浏览器支持。');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
videoPlayer.style.display = 'block';
|
|
||||||
description.style.display = 'none';
|
|
||||||
} else {
|
|
||||||
console.error('不支持播放该视频格式。');
|
|
||||||
alert('不支持播放该视频格式。');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById('playButton').addEventListener('click', playVideo);
|
|
||||||
</script>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
112
web.py → web2.py
112
web.py → web2.py
@@ -3,6 +3,7 @@ from fastapi.responses import FileResponse, JSONResponse
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import asyncio
|
import asyncio
|
||||||
import re
|
import re
|
||||||
|
from typing import Optional
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
|
||||||
app = FastAPI(title="多进程录制控制台")
|
app = FastAPI(title="多进程录制控制台")
|
||||||
@@ -14,8 +15,79 @@ DEFAULT_LOG_DIR = BASE_DIR / ".pm2" / "logs"
|
|||||||
|
|
||||||
MAX_LOG_LINES = 300
|
MAX_LOG_LINES = 300
|
||||||
|
|
||||||
# 支持的进程列表
|
|
||||||
VALID_PROCESSES = {"tiktok", "youtube", "obs"}
|
def _label_short(prefix: str) -> str:
|
||||||
|
return {"tiktok": "TikTok", "youtube": "YouTube", "obs": "OBS", "web": "Web 控制台"}.get(prefix, prefix)
|
||||||
|
|
||||||
|
|
||||||
|
def discover_script_entries() -> tuple:
|
||||||
|
"""扫描项目根目录自动识别入口:tiktok*.py、youtube*.py、obs*.sh,以及当前本文件(Web 控制台)。PM2 名 = 主文件名(无扩展名)。"""
|
||||||
|
base = BASE_DIR
|
||||||
|
web_path = Path(__file__).resolve()
|
||||||
|
entries: list[dict] = []
|
||||||
|
|
||||||
|
for path in sorted(base.glob("tiktok*.py")):
|
||||||
|
if path.is_file() and path.resolve() != web_path:
|
||||||
|
entries.append({"script": path.name, "label": _label_short("tiktok")})
|
||||||
|
|
||||||
|
for path in sorted(base.glob("youtube*.py")):
|
||||||
|
if path.is_file() and path.resolve() != web_path:
|
||||||
|
entries.append({"script": path.name, "label": _label_short("youtube")})
|
||||||
|
|
||||||
|
for path in sorted(base.glob("obs*.sh")):
|
||||||
|
if path.is_file():
|
||||||
|
entries.append({"script": path.name, "label": _label_short("obs")})
|
||||||
|
|
||||||
|
entries.append({"script": web_path.name, "label": _label_short("web")})
|
||||||
|
|
||||||
|
return tuple(entries)
|
||||||
|
|
||||||
|
|
||||||
|
SCRIPT_ENTRIES = discover_script_entries()
|
||||||
|
|
||||||
|
|
||||||
|
def pm2_name_from_script(script: str) -> str:
|
||||||
|
return Path(script).stem
|
||||||
|
|
||||||
|
|
||||||
|
def _stem(script: str) -> str:
|
||||||
|
return Path(script).stem
|
||||||
|
|
||||||
|
|
||||||
|
def is_youtube_script(script: str) -> bool:
|
||||||
|
return script.endswith(".py") and _stem(script).startswith("youtube")
|
||||||
|
|
||||||
|
|
||||||
|
def is_tiktok_script(script: str) -> bool:
|
||||||
|
return script.endswith(".py") and _stem(script).startswith("tiktok")
|
||||||
|
|
||||||
|
|
||||||
|
def _build_process_monitor():
|
||||||
|
return tuple(
|
||||||
|
{
|
||||||
|
"pm2": pm2_name_from_script(e["script"]),
|
||||||
|
"script": e["script"],
|
||||||
|
"label": e["label"],
|
||||||
|
}
|
||||||
|
for e in SCRIPT_ENTRIES
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
PROCESS_MONITOR = _build_process_monitor()
|
||||||
|
VALID_PROCESSES = frozenset(p["pm2"] for p in PROCESS_MONITOR)
|
||||||
|
|
||||||
|
URL_CONFIG_PM2 = frozenset(
|
||||||
|
pm2_name_from_script(e["script"])
|
||||||
|
for e in SCRIPT_ENTRIES
|
||||||
|
if is_tiktok_script(e["script"]) or is_youtube_script(e["script"])
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def script_for_pm2(pm2: str) -> Optional[str]:
|
||||||
|
for p in PROCESS_MONITOR:
|
||||||
|
if p["pm2"] == pm2:
|
||||||
|
return p["script"]
|
||||||
|
return None
|
||||||
|
|
||||||
# CORS
|
# CORS
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
@@ -125,8 +197,9 @@ async def get_process_info(process: str) -> dict:
|
|||||||
# ---------------- 配置路由(youtube.ini,仅 youtube) ----------------
|
# ---------------- 配置路由(youtube.ini,仅 youtube) ----------------
|
||||||
@app.get("/get_config")
|
@app.get("/get_config")
|
||||||
async def get_config(process: str = Query(..., description="进程名")):
|
async def get_config(process: str = Query(..., description="进程名")):
|
||||||
if process != "youtube":
|
sc = script_for_pm2(process)
|
||||||
return JSONResponse({"error": "仅 youtube 支持此配置编辑"}, status_code=400)
|
if not sc or not is_youtube_script(sc):
|
||||||
|
return JSONResponse({"error": "仅 youtube*.py 对应进程支持 youtube.ini 编辑"}, status_code=400)
|
||||||
|
|
||||||
config_file = CONFIG_DIR / "youtube.ini"
|
config_file = CONFIG_DIR / "youtube.ini"
|
||||||
if not config_file.exists():
|
if not config_file.exists():
|
||||||
@@ -139,8 +212,9 @@ async def get_config(process: str = Query(..., description="进程名")):
|
|||||||
|
|
||||||
@app.post("/save_config")
|
@app.post("/save_config")
|
||||||
async def save_config(request: Request, process: str = Query(..., description="进程名")):
|
async def save_config(request: Request, process: str = Query(..., description="进程名")):
|
||||||
if process != "youtube":
|
sc = script_for_pm2(process)
|
||||||
return JSONResponse({"error": "仅 youtube 支持此配置编辑"}, status_code=400)
|
if not sc or not is_youtube_script(sc):
|
||||||
|
return JSONResponse({"error": "仅 youtube*.py 对应进程支持 youtube.ini 编辑"}, status_code=400)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = await request.json()
|
data = await request.json()
|
||||||
@@ -159,8 +233,8 @@ async def save_config(request: Request, process: str = Query(..., description="
|
|||||||
# ---------------- 配置路由(URL_config.ini,youtube 和 tiktok 共享) ----------------
|
# ---------------- 配置路由(URL_config.ini,youtube 和 tiktok 共享) ----------------
|
||||||
@app.get("/get_url_config")
|
@app.get("/get_url_config")
|
||||||
async def get_url_config(process: str = Query(..., description="进程名")):
|
async def get_url_config(process: str = Query(..., description="进程名")):
|
||||||
if process not in ["youtube", "tiktok"]:
|
if process not in URL_CONFIG_PM2:
|
||||||
return JSONResponse({"error": "仅 youtube 和 tiktok 支持此配置编辑"}, status_code=400)
|
return JSONResponse({"error": "仅 tiktok*.py / youtube*.py 对应进程支持 URL 配置编辑"}, status_code=400)
|
||||||
|
|
||||||
config_file = CONFIG_DIR / "URL_config.ini"
|
config_file = CONFIG_DIR / "URL_config.ini"
|
||||||
if not config_file.exists():
|
if not config_file.exists():
|
||||||
@@ -173,8 +247,8 @@ async def get_url_config(process: str = Query(..., description="进程名")):
|
|||||||
|
|
||||||
@app.post("/save_url_config")
|
@app.post("/save_url_config")
|
||||||
async def save_url_config(request: Request, process: str = Query(..., description="进程名")):
|
async def save_url_config(request: Request, process: str = Query(..., description="进程名")):
|
||||||
if process not in ["youtube", "tiktok"]:
|
if process not in URL_CONFIG_PM2:
|
||||||
return JSONResponse({"error": "仅 youtube 和 tiktok 支持此配置编辑"}, status_code=400)
|
return JSONResponse({"error": "仅 tiktok*.py / youtube*.py 对应进程支持 URL 配置编辑"}, status_code=400)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = await request.json()
|
data = await request.json()
|
||||||
@@ -210,14 +284,15 @@ async def stop(process: str = Query(..., description="进程名")):
|
|||||||
# 第二步:等待 PM2 信号传播(通常 1~3 秒)
|
# 第二步:等待 PM2 信号传播(通常 1~3 秒)
|
||||||
await asyncio.sleep(2.0)
|
await asyncio.sleep(2.0)
|
||||||
|
|
||||||
# 第三步:主动查找并杀死 ffmpeg
|
# 第三步:录制类进程才强杀 ffmpeg(web* 控制台不杀 ffmpeg)
|
||||||
|
if not process.startswith("web"):
|
||||||
await force_kill_ffmpeg(process)
|
await force_kill_ffmpeg(process)
|
||||||
|
|
||||||
# 第四步:再等一小会儿,确认
|
# 第四步:再等一小会儿,确认
|
||||||
await asyncio.sleep(1.0)
|
await asyncio.sleep(1.0)
|
||||||
|
|
||||||
return JSONResponse({
|
return JSONResponse({
|
||||||
"message": "已执行 stop + 强杀 ffmpeg",
|
"message": "已执行 stop" + ("" if process.startswith("web") else " + 强杀 ffmpeg"),
|
||||||
"pm2_output": pm2_output,
|
"pm2_output": pm2_output,
|
||||||
"note": "如果仍有残留,可多次点击停止或检查 pm2 logs"
|
"note": "如果仍有残留,可多次点击停止或检查 pm2 logs"
|
||||||
})
|
})
|
||||||
@@ -228,7 +303,7 @@ async def restart(process: str = Query(..., description="进程名")):
|
|||||||
if process not in VALID_PROCESSES:
|
if process not in VALID_PROCESSES:
|
||||||
return JSONResponse({"output": f"无效进程名: {process}"}, status_code=400)
|
return JSONResponse({"output": f"无效进程名: {process}"}, status_code=400)
|
||||||
output = await run_pm2(f"restart {process}")
|
output = await run_pm2(f"restart {process}")
|
||||||
# 重启后也建议清理一次残留(视情况可选)
|
if not process.startswith("web"):
|
||||||
await force_kill_ffmpeg(process)
|
await force_kill_ffmpeg(process)
|
||||||
return JSONResponse({"output": output})
|
return JSONResponse({"output": output})
|
||||||
|
|
||||||
@@ -261,6 +336,17 @@ async def status(process: str = Query(..., description="进程名")):
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------- 进程列表(供前端同步,由 discover_script_entries 自动扫描) ----------------
|
||||||
|
@app.get("/process_monitor")
|
||||||
|
async def process_monitor():
|
||||||
|
return {
|
||||||
|
"entries": [
|
||||||
|
{"pm2": p["pm2"], "script": p["script"], "label": p["label"]}
|
||||||
|
for p in PROCESS_MONITOR
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# ---------------- 首页 ----------------
|
# ---------------- 首页 ----------------
|
||||||
@app.get("/")
|
@app.get("/")
|
||||||
async def index():
|
async def index():
|
||||||
2
web2.sh
Normal file
2
web2.sh
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# Ubuntu:本分支控制台入口(与无「2」分支的 web / youtube.py 等区分)
|
||||||
|
uvicorn web2:app --host 0.0.0.0 --port 8001 --reload
|
||||||
Reference in New Issue
Block a user