我用的koa框架,app.js之类的省略。系统用的debian,浏览器用的firefox,windows应该也能用。
firefox要装插件ModHeader修改http请求头,因为有的视频网址有限制。再装一个CORS Unblock插件。还要把浏览器的打开后禁止自动播放禁止。取消禁止视频全屏。
手机控制端:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title></title>
<style type="text/css">
body{text-align:center;margin:0 auto;}
</style>
<style type="text/css">
.button {
//padding: 10px 0;
margin: 10px 20px;
font-size: 18px;
color: #000;
border-radius: 25px;
height: 25px;
width: 100px;
border: 0px;
background-color: #eee;
box-shadow: 0 8px 16px #888;
outline: 1px;
cursor: pointer;
}
.button:active {
color: #00f;
background-color: #0f0;
transform: translateY(4px);
box-shadow: 0 4px 16px #ff0
}
body {
padding: 0 0
}
@media only screen and (min-width: 500px) {
body {
padding: 0 200px
}
}
</style>
</head>
<body>
<script src="/p/js/vue.min.js"></script>
<script src="/p/js/tv.js"></script>
<br><br>
<div id="app">
<div v-for="(v,k,i) in tvurl" style="display:inline-block">
<div class="button" v-bind:id="'btn'+i" v-on:click="dakai(v,i)">{{k}}</div>
</div><br><br><br><br><br><br>
<div v-for="item in caidan">
<div v-for="subitem in item" style="display:inline-block">
<button class="button" v-on:click="fasong($event)">{{subitem}}</button>
</div><br>
</div>
</div>
<script>
vm = new Vue({
el: '#app',
data: {
tvurl: tv ,
caidan:[
["打开","刷新","关闭"],
[],
["暂停","播放"],
[],
["全屏","退出全屏"],
["上"],
["下"],
["加","减"],
[],
["+","-"]
],
},
methods: {
fasong: function (e){
console.log(e.currentTarget.innerText)
fetch("/yaokongfasong/"+e.currentTarget.innerText)
}
}
})
function dakai(v,i){
fetch("/yaokongfasong/btn"+i)
}
//firefox about:config full-screen-api.allow-trusted-requests-only false full-screen-api.approval-required
</script>
</body>
</html>
koa控制器:
const util = require('util');
const exec = util.promisify(require('child_process').exec);
const WebSocket = require('ws');
const url = require('url');
let yaokongws={};
function sleep(time){
return new Promise((resolve) => setTimeout(resolve, time));
}
let wss = new WebSocket.Server({
server: server2
});
wss.on('connection', function (ws,req) {
let location = url.parse(req.url, true);
console.log(req.url);
if (location.pathname !== '/ws/yaokong') {
ws.close(4000, 'Invalid URL');
return;
}
})
yaokongws.ws=wss;
module.exports = {
'GET /yaokongfasong/:cmd': async (ctx, next) => {
var cmd = ctx.params.cmd;
if (cmd==="+"){
await exec("amixer sset Master 5%+")
return
}
else if (cmd==="-"){
const { stdout, stderr } =await exec("amixer sget Master" )
let aa=stdout.match(/ \[([0-9]{1,3})%\] /);
bb=Number(aa[1])-5
bb=bb<0?0:bb;
await exec("amixer sset Master "+ bb +"%")
return
}
else if (cmd==="播放"){
await exec("xdotool mousemove_relative 1 1")
}
else if (cmd==="打开"){
try{
const { stdout, stderr }=await exec("ps -e|grep -s firefox");
}
catch(e){
exec("firefox");
await sleep(2500);
}
exec("firefox http://192.168.123.200:3000/p/iptv_.htm &") ;
}
try{
console.log(yaokongws.ws.clients.size);
[...yaokongws.ws.clients][yaokongws.ws.clients.size-1].send(cmd);
}
catch(e){console.log("cuo")}
}
}
电脑端网页:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<style type="text/css">
body{text-align:center;margin:0 auto;}
.button {
padding: 10px 0;
margin: 20;
font-size: 18px;
color: #000;
border-radius: 25px;
height: 25px;
width: 100px;
border: 0px;
//background-color: #eee;
box-shadow: 0 8px 16px #888;
//outline: none;
}
.vd{
width: 100vw;
height: calc(100vw * 0.5625);
object-fit: fill;
}
@media only screen and (min-width: 1280px) {
.vd{
width: 1280px;
height: 720px;
object-fit: fill;
}
}
</style>
</head>
<body>
<script src="/p/js/hls.js"></script>
<script src="/p/js/flv.js"></script>
<script src="/p/js/vue.min.js"></script>
<div>
<video class="vd" id="videoElement" allowfullscreen="true" ></video>
<br><br>
</div>
<div id="app">
<div v-for="(v,k,i) in tvurl" style="display:inline-block">
<div class="button" v-bind:id="'btn'+i" v-on:click="dakai(v,i)">{{k}}</div>
</div>
</div><br>
<input type="text" id="tvu" />
<input type="button" id="dakai" onclick="dakai(tvu.value)" value="打开" /><br><br>
<script src="/p/js/tv.js"></script>
<script>
var video = document.getElementById('videoElement');
var curidx=3;
var flvPlayer = null;
var hls=null;
function dakai(url,idx) {
curidx=idx;
if (url.endsWith("flv")) {
if (flvPlayer) {
this.flvPlayer.pause();
this.flvPlayer.unload();
this.flvPlayer.detachMediaElement();
this.flvPlayer.destroy();
this.flvPlayer = null;
}
flvPlayer = flvjs.createPlayer({
type: 'flv',
url: url
});
flvPlayer.attachMediaElement(video);
flvPlayer.load();
flvPlayer.play();
}
else {
if (flvPlayer) {
this.flvPlayer.pause();
this.flvPlayer.unload();
this.flvPlayer.detachMediaElement();
this.flvPlayer.destroy();
this.flvPlayer = null;
}
if (hls) {
hls.stopLoad();
hls.detachMedia();
hls.destroy();
hls = null;
}
hls = new window.Hls()
hls.attachMedia(video)
hls.on(Hls.Events.MANIFEST_PARSED, function () {
video.play()
})
hls.loadSource(url)
}
}
vm = new Vue({
el: '#app',
data: { tvurl: tv }
})
/*
cdns = "http://www.baidu.com.cdns.cfd/url/?id=";
pms = []
t = []
for (let i in j) {
pms.push(fetch(cdns + j[i]));
t.push(i);
}
Promise.all(pms).then(a => a.forEach((b, i) => { vm.tvurl[t[i]] = b.url.substr(37) }))
*/
fetch("http://www.baidu.com.cdns.cfd/html/?id=hebws").then(b => vm.$set(vm.tvurl,"河北",b.url.substr(37)));
async function tianjia(ming,can,ur,matchurl="<source src="){
const response = await fetch(ur+can);
const body = await response.text();
let re=new RegExp(matchurl+'\\"(.*?)\\"');
let aa=body.match(re);
vm.$set(vm.tvurl,ming,aa[1]);
}
a={
凤凰中文2: "fhzw",
凤凰资讯: "fhzx",
}
for (let b in a){
tianjia(b,a[b],'http://player.200877926.top/1691/_others/fh.php?id=')
}
c={
湖南: "hunan",
cctv2: "cctv2",
江苏: "jiangsu",
凤凰中文: "fhzw",
btv:"beijing",
天津:"tianjin",
cctvfyyy:"cctvfyyy",
}
for (let b in c){
tianjia(b,c[b],'http://player.200877926.top/1691/cq/cqyx.php?id=','\\"url\\": ')
}
tianjia("东方卫视","dongfang","http://player.200877926.top/1691/sh/shanghai.php?id=")
tianjia("cctv","cctv1","http://player.200877926.top/1691/sd/weihai.php?id=")
window.onload = () => dakai(vm.tvurl['浙江'],3);
ws=new WebSocket('ws://127.0.0.1:3000/ws/yaokong') ;
ws.onmessage=function onMessage(event){
minglingduixiang={
上:()=>document.querySelector('#btn'+(curidx-1)).click(),
下:()=>document.querySelector('#btn'+(curidx+1)).click(),
加:()=>video.volume=video.volume+0.05,
减:()=>video.volume=video.volume-0.05,
全屏:()=>video.requestFullscreen(),
退出全屏:()=>document.exitFullscreen(),
暂停:()=>video.pause(),
播放:()=>video.play(),
刷新:()=>window.location.reload(),
关闭:()=>window.close(),
}
if(minglingduixiang[event.data]){
minglingduixiang[event.data]();
return;
}
if(event.data.startsWith("btn")){
document.querySelector("#"+event.data).click();
}
}
</script>
</body>
</html>
tv.js:
tv = {
cctv: '',
cctv2: ' ',
凤凰中文: ' ',
河北: '',
河北农民: 'https://event.pull.hebtv.com/jishi/nongminpindao.m3u8',
btv: '',
东方卫视: ' ',
天津:'',
浙江: 'http://hw-m-l.cztv.com/channels/lantian/channel01/1080p.m3u8',
湖南: '',
江苏: '',
HKS: 'http://zhibo.hkstv.tv/livestream/mutfysrq/playlist.m3u8',
郑州广播: 'https://live.ximalaya.com/radio-first-page-app/live/476/64.m3u8?transcode=ts',
怀集广播: 'https://live.ximalaya.com/radio-first-page-app/live/966/64.m3u8?transcode=ts',
经典fm: 'https://live.ximalaya.com/radio-first-page-app/live/2689/64.m3u8?transcode=ts',
//保定: 'http://live.bdgdw.com/channel1/sd/live.m3u8?_upt=743267691684327305',
俄罗斯: 'https://brics.bonus-tv.ru/cdn/brics/chinese/tracks-v1a1/mono.m3u8',
ndt: 'https://ntd02.akamaized.net/NTDA/tracks-v4a1/mono.m3u8',
//大爱2: 'https://pulltv2.wanfudaluye.com/live/tv2.m3u8',
cetv1:'http://txycsbl.centv.cn/zb/0628cetv1.m3u8',
}
有空把websocket改成SSE