mirror of
https://github.com/Monibuca/plugin-jessica.git
synced 2025-09-26 20:11:15 +08:00
first commit
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
.vscode
|
||||
node_modules
|
15
README.md
15
README.md
@@ -1,2 +1,17 @@
|
||||
# jessicaplugin
|
||||
websocket raw data protocol plugin for monibuca
|
||||
|
||||
通过Websocket传输音视频数据,使用Jessibuca播放器进行播放。播放器已内置在该插件中。该插件带有UI界面,可以显示所有的房间,并且可以预览房间视频。
|
||||
|
||||
## 插件名称
|
||||
|
||||
Jessica
|
||||
|
||||
## 配置
|
||||
|
||||
目前仅有的配置是监听的端口号
|
||||
|
||||
```toml
|
||||
[Plugins.Jessica]
|
||||
ListenAddr = ":8080"
|
||||
```
|
93
dashboard/components/Jessibuca.vue
Normal file
93
dashboard/components/Jessibuca.vue
Normal file
@@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<Modal
|
||||
v-bind="$attrs"
|
||||
draggable
|
||||
v-on="$listeners"
|
||||
:title="url"
|
||||
@on-ok="onClosePreview"
|
||||
@on-cancel="onClosePreview"
|
||||
>
|
||||
<canvas id="canvas" width="488" height="275" style="background: black" />
|
||||
<div slot="footer">
|
||||
<Button v-if="audioEnabled" @click="turnOff" icon="md-volume-up" />
|
||||
<Button v-else @click="turnOn" icon="md-volume-off"></Button>
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
let h5lc = null;
|
||||
export default {
|
||||
name: "Jessibuca",
|
||||
props: {
|
||||
audioCodec: String,
|
||||
videoCodec: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
audioEnabled: true,
|
||||
url: "",
|
||||
decoderTable: {
|
||||
AAC_AVC: "ff",
|
||||
AAC_H265: "hevc_aac",
|
||||
MP3_AVC: "ff_mp3",
|
||||
MP3_H265: "hevc_mp3"
|
||||
}
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
audioEnabled(value) {
|
||||
h5lc.audioEnabled(value);
|
||||
},
|
||||
decoder(value) {
|
||||
if (h5lc) {
|
||||
h5lc.destroy();
|
||||
}
|
||||
h5lc = new window.Jessibuca({
|
||||
canvas: document.getElementById("canvas"),
|
||||
decoder: value
|
||||
});
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
decoder() {
|
||||
return (
|
||||
"jessibuca/" +
|
||||
this.decoderTable[this.audioCodec + "_" + this.videoCodec] +
|
||||
".js"
|
||||
);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
let s = document.createElement("script");
|
||||
s.src = "/jessibuca/renderer.js";
|
||||
s.onload = function() {
|
||||
s.onload = null;
|
||||
h5lc = new window.Jessibuca({
|
||||
canvas: document.getElementById("canvas"),
|
||||
decoder: "jessibuca/ff.js"
|
||||
});
|
||||
};
|
||||
this.$root.$el.append(s);
|
||||
},
|
||||
methods: {
|
||||
play(url) {
|
||||
this.url = url;
|
||||
h5lc.play(url);
|
||||
},
|
||||
onClosePreview() {
|
||||
h5lc.close();
|
||||
},
|
||||
destroy() {
|
||||
h5lc.destroy();
|
||||
},
|
||||
turnOn() {
|
||||
this.audioEnabled = true;
|
||||
},
|
||||
turnOff() {
|
||||
this.audioEnabled = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
18
dashboard/components/StartTime.vue
Normal file
18
dashboard/components/StartTime.vue
Normal file
@@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<Poptip trigger="hover" :content="'⌚️'+ new Date(value).toLocaleString()">
|
||||
<Time :time="new Date(value)"></Time>
|
||||
</Poptip>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "StartTime",
|
||||
props:{
|
||||
value:String
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
63
dashboard/components/Subscribers.vue
Normal file
63
dashboard/components/Subscribers.vue
Normal file
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<Modal v-bind="$attrs" draggable v-on="$listeners" title="查看订阅者">
|
||||
<Table :columns="subtableColumns" :data="data"></Table>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import StartTime from "./StartTime"
|
||||
export default {
|
||||
props: {
|
||||
data: Array
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
subtableColumns: [
|
||||
{
|
||||
title: "类型",
|
||||
key: "Type"
|
||||
},
|
||||
{
|
||||
title: "Name",
|
||||
key: "ID"
|
||||
},
|
||||
{
|
||||
title: "订阅时间",
|
||||
render(h, { row }) {
|
||||
return h(StartTime, {
|
||||
props: {
|
||||
value: row.SubscribeTime
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "丢帧",
|
||||
render(h, { row }) {
|
||||
return h(
|
||||
"span",
|
||||
row.TotalPacket ? row.TotalDrop + "/" + row.TotalPacket : ""
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "Buffer",
|
||||
render(h, { row }) {
|
||||
return h("Progress", {
|
||||
props: {
|
||||
percent: Math.floor((row.BufferLength * 99) / 1024),
|
||||
"text-inside": true,
|
||||
"stroke-width": 20,
|
||||
"stroke-color": ["#87d068", "#ff0000"]
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
157
dashboard/index.vue
Normal file
157
dashboard/index.vue
Normal file
@@ -0,0 +1,157 @@
|
||||
<template>
|
||||
<div class="layout">
|
||||
<Card v-for="item in Rooms" :key="item.StreamPath" class="room">
|
||||
<p slot="title">{{typeMap[item.Type]||item.Type}}{{item.StreamPath}}</p>
|
||||
<StartTime slot="extra" :value="item.StartTime"></StartTime>
|
||||
<p>
|
||||
{{SoundFormat(item.AudioInfo.SoundFormat)}} {{item.AudioInfo.PacketCount}}
|
||||
{{SoundRate(item.AudioInfo.SoundRate)}} 声道:{{item.AudioInfo.SoundType}}
|
||||
</p>
|
||||
<p>
|
||||
{{CodecID(item.VideoInfo.CodecID)}} {{item.VideoInfo.PacketCount}}
|
||||
{{item.VideoInfo.SPSInfo.Width}}x{{item.VideoInfo.SPSInfo.Height}}
|
||||
</p>
|
||||
<ButtonGroup size="small">
|
||||
<Button @click="onShowDetail(item)" icon="ios-people">{{getSubscriberCount(item)}}</Button>
|
||||
<Button v-if="item.Type" @click="preview(item)" icon="md-eye"></Button>
|
||||
<!-- <Button
|
||||
@click="stopRecord(item)"
|
||||
class="recording"
|
||||
v-if="isRecording(item)"
|
||||
icon="ios-radio-button-on"
|
||||
></Button>
|
||||
<Button @click="record(item)" v-else icon="ios-radio-button-on"></Button>-->
|
||||
</ButtonGroup>
|
||||
</Card>
|
||||
<div v-if="Rooms.length==0" class="empty">
|
||||
<Icon type="md-wine" size="50" />没有任何房间
|
||||
</div>
|
||||
<Jessibuca
|
||||
ref="jessibuca"
|
||||
v-model="showPreview"
|
||||
:videoCodec="currentStream && CodecID(currentStream.VideoInfo.CodecID)"
|
||||
:audioCodec="currentStream && SoundFormat(currentStream.AudioInfo.SoundFormat)"
|
||||
></Jessibuca>
|
||||
<Subscribers
|
||||
:data="currentStream && currentStream.SubscriberInfo || []"
|
||||
v-model="showSubscribers"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const SoundFormat = {
|
||||
0: "Linear PCM, platform endian",
|
||||
1: "ADPCM",
|
||||
2: "MP3",
|
||||
3: "Linear PCM, little endian",
|
||||
4: "Nellymoser 16kHz mono",
|
||||
5: "Nellymoser 8kHz mono",
|
||||
6: "Nellymoser",
|
||||
7: "G.711 A-law logarithmic PCM",
|
||||
8: "G.711 mu-law logarithmic PCM",
|
||||
9: "reserved",
|
||||
10: "AAC",
|
||||
11: "Speex",
|
||||
14: "MP3 8Khz",
|
||||
15: "Device-specific sound"
|
||||
};
|
||||
const CodecID = {
|
||||
1: "JPEG (currently unused)",
|
||||
2: "Sorenson H.263",
|
||||
3: "Screen video",
|
||||
4: "On2 VP6",
|
||||
5: "On2 VP6 with alpha channel",
|
||||
6: "Screen video version 2",
|
||||
7: "AVC",
|
||||
12: "H265"
|
||||
};
|
||||
import Jessibuca from "./components/Jessibuca";
|
||||
import Subscribers from "./components/Subscribers";
|
||||
let summaryES = null;
|
||||
export default {
|
||||
components: {
|
||||
Jessibuca,
|
||||
Subscribers
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showPreview: false,
|
||||
currentStream: null,
|
||||
showSubscribers: false,
|
||||
typeMap: {
|
||||
Receiver: "📡",
|
||||
FlvFile: "🎥",
|
||||
TS: "🎬",
|
||||
HLS: "🍎",
|
||||
"": "⏳",
|
||||
Match365: "🏆",
|
||||
RTMP: "🚠"
|
||||
},
|
||||
Rooms: []
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
getSubscriberCount(item) {
|
||||
if (
|
||||
this.currentStream &&
|
||||
this.currentStream.StreamPath == item.StreamPath
|
||||
) {
|
||||
this.currentStream = item;
|
||||
}
|
||||
return item.SubscriberInfo ? item.SubscriberInfo.length : 0;
|
||||
},
|
||||
preview(item) {
|
||||
this.currentStream = item;
|
||||
this.$nextTick(() =>
|
||||
this.$refs.jessibuca.play(
|
||||
"ws://" + location.hostname + ":8080/" + item.StreamPath
|
||||
)
|
||||
);
|
||||
this.showPreview = true;
|
||||
},
|
||||
SoundFormat(soundFormat) {
|
||||
return SoundFormat[soundFormat];
|
||||
},
|
||||
CodecID(codec) {
|
||||
return CodecID[codec];
|
||||
},
|
||||
SoundRate(rate) {
|
||||
return rate > 1000 ? rate / 1000 + "kHz" : rate + "Hz";
|
||||
},
|
||||
onShowDetail(item) {
|
||||
this.showSubscribers = true;
|
||||
this.currentStream = item;
|
||||
},
|
||||
fetchSummary() {
|
||||
summaryES = new EventSource("//" + location.host + "/api/summary");
|
||||
summaryES.onmessage = evt => {
|
||||
if (!evt.data) return;
|
||||
let summary = JSON.parse(evt.data);
|
||||
summary.Address = location.hostname;
|
||||
if (!summary.Rooms) summary.Rooms = [];
|
||||
summary.Rooms.sort((a, b) =>
|
||||
a.StreamPath > b.StreamPath ? 1 : -1
|
||||
);
|
||||
this.Rooms = summary.Rooms;
|
||||
};
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchSummary();
|
||||
},
|
||||
deactivated() {
|
||||
summaryES.close();
|
||||
this.$refs.jessibuca.destroy();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@import url("/iview.css");
|
||||
.layout {
|
||||
padding-bottom: 30px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
</style>
|
9606
dashboard/package-lock.json
generated
Normal file
9606
dashboard/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
18
dashboard/package.json
Normal file
18
dashboard/package.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "dashboard",
|
||||
"version": "1.0.0",
|
||||
"description": "dashboard of jessica plugin for monibuca",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "vue-cli-service build --dest ui --target wc --name plugin-jessica index.vue"
|
||||
},
|
||||
"author": "dexter",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@vue/cli-service": "^4.2.3",
|
||||
"vue-template-compiler": "^2.6.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"view-design": "^4.1.3"
|
||||
}
|
||||
}
|
25
dashboard/public/ff.js
Normal file
25
dashboard/public/ff.js
Normal file
File diff suppressed because one or more lines are too long
25
dashboard/public/ff_mp3.js
Normal file
25
dashboard/public/ff_mp3.js
Normal file
File diff suppressed because one or more lines are too long
22
dashboard/public/hevc_aac.js
Normal file
22
dashboard/public/hevc_aac.js
Normal file
File diff suppressed because one or more lines are too long
21
dashboard/public/hevc_mp3.js
Normal file
21
dashboard/public/hevc_mp3.js
Normal file
File diff suppressed because one or more lines are too long
465
dashboard/public/renderer.js
Normal file
465
dashboard/public/renderer.js
Normal file
@@ -0,0 +1,465 @@
|
||||
window.AudioContext = window.AudioContext || window.webkitAudioContext;
|
||||
function Jessibuca(opt) {
|
||||
this.audioContext = new window.AudioContext()
|
||||
this.canvasElement = opt.canvas;
|
||||
this.contextOptions = opt.contextOptions;
|
||||
this.videoBuffer = opt.videoBuffer || 1
|
||||
if (!opt.forceNoGL) this.initContextGL();
|
||||
|
||||
if (this.contextGL) {
|
||||
this.initProgram();
|
||||
this.initBuffers();
|
||||
this.initTextures();
|
||||
};
|
||||
this.decoderWorker = new Worker(opt.decoder || 'ff.js')
|
||||
var _this = this
|
||||
function draw(output) {
|
||||
_this.drawNextOutputPicture(_this.width, _this.height, null, output)
|
||||
postMessage({ cmd: "setBuffer", buffer: output }, '*', [output[0].buffer, output[1].buffer, output[2].buffer])
|
||||
}
|
||||
this.decoderWorker.onmessage = function (event) {
|
||||
var msg = event.data
|
||||
switch (msg.cmd) {
|
||||
case "init":
|
||||
console.log("decoder worker init")
|
||||
postMessage({ cmd: "setVideoBuffer", time: _this.videoBuffer }, "*")
|
||||
if (_this.onLoad) {
|
||||
_this.onLoad()
|
||||
delete _this.onLoad;
|
||||
}
|
||||
break
|
||||
case "initSize":
|
||||
_this.width = msg.w
|
||||
_this.height = msg.h
|
||||
if (_this.isWebGL()) {
|
||||
// var buffer = new ArrayBuffer(msg.w * msg.h + (msg.w * msg.h >> 1))
|
||||
// this.postMessage({ cmd: "setBuffer", buffer: buffer }, [buffer])
|
||||
}
|
||||
else {
|
||||
_this.initRGB(msg.w, msg.h)
|
||||
}
|
||||
break
|
||||
case "render":
|
||||
if (_this.onPlay) {
|
||||
_this.onPlay()
|
||||
delete _this.onPlay;
|
||||
}
|
||||
// if (msg.compositionTime) {
|
||||
// console.log(msg.compositionTime)
|
||||
// setTimeout(draw, msg.compositionTime, msg.output)
|
||||
// } else {
|
||||
// draw(msg.output)
|
||||
// }
|
||||
draw(msg.output)
|
||||
break
|
||||
case "initAudio":
|
||||
_this.initAudioPlay(msg.frameCount, msg.samplerate, msg.channels)
|
||||
break
|
||||
case "playAudio":
|
||||
_this.playAudio(msg.buffer)
|
||||
break
|
||||
case "print":
|
||||
console.log(msg.text);
|
||||
break
|
||||
case "printErr":
|
||||
console.error(msg.text);
|
||||
break
|
||||
}
|
||||
}
|
||||
};
|
||||
function _unlock(context) {
|
||||
context.resume();
|
||||
var source = context.createBufferSource();
|
||||
source.buffer = context.createBuffer(1, 1, 22050);
|
||||
source.connect(context.destination);
|
||||
if (source.noteOn)
|
||||
source.noteOn(0);
|
||||
else
|
||||
source.start(0);
|
||||
}
|
||||
// document.addEventListener("mousedown", _unlock, true);
|
||||
// document.addEventListener("touchend", _unlock, true);
|
||||
Jessibuca.prototype.audioEnabled = function (flag) {
|
||||
if (flag) {
|
||||
this.audioContext.resume();
|
||||
} else {
|
||||
this.audioContext.suspend();
|
||||
}
|
||||
}
|
||||
Jessibuca.prototype.playAudio = function (data) {
|
||||
var context = this.audioContext;
|
||||
var isPlaying = false;
|
||||
var isDecoding = false;
|
||||
if (!context) return false;
|
||||
var audioBuffers = [];
|
||||
var decodeQueue = []
|
||||
var _this = this
|
||||
var playNextBuffer = function (e) {
|
||||
// isPlaying = false;
|
||||
if (audioBuffers.length) {
|
||||
playBuffer(audioBuffers.shift())
|
||||
}
|
||||
//if (audioBuffers.length > 1) audioBuffers.shift();
|
||||
};
|
||||
var playBuffer = function (buffer) {
|
||||
isPlaying = true;
|
||||
var audioBufferSouceNode = context.createBufferSource();
|
||||
audioBufferSouceNode.buffer = buffer;
|
||||
audioBufferSouceNode.connect(context.destination);
|
||||
// audioBufferSouceNode.onended = playNextBuffer;
|
||||
audioBufferSouceNode.start();
|
||||
if (!_this.audioInterval) {
|
||||
_this.audioInterval = setInterval(playNextBuffer, buffer.duration * 1000 - 1);
|
||||
}
|
||||
// setTimeout(playNextBuffer, buffer.duration * 1000)
|
||||
}
|
||||
var decodeAudio = function () {
|
||||
if (decodeQueue.length) {
|
||||
context.decodeAudioData(decodeQueue.shift(), tryPlay, decodeAudio);
|
||||
} else {
|
||||
isDecoding = false
|
||||
}
|
||||
}
|
||||
var tryPlay = function (buffer) {
|
||||
decodeAudio()
|
||||
if (isPlaying) {
|
||||
audioBuffers.push(buffer);
|
||||
} else {
|
||||
playBuffer(buffer)
|
||||
}
|
||||
}
|
||||
var playAudio = function (data) {
|
||||
decodeQueue.push(...data)
|
||||
if (!isDecoding) {
|
||||
isDecoding = true
|
||||
decodeAudio()
|
||||
}
|
||||
}
|
||||
this.playAudio = playAudio
|
||||
playAudio(data)
|
||||
}
|
||||
Jessibuca.prototype.initAudioPlay = function (frameCount, samplerate, channels) {
|
||||
var context = this.audioContext;
|
||||
var isPlaying = false;
|
||||
var audioBuffers = [];
|
||||
if (!context) return false;
|
||||
var resampled = samplerate < 22050;
|
||||
var audioBuffer = resampled ? context.createBuffer(channels, frameCount << 1, samplerate << 1) : context.createBuffer(channels, frameCount, samplerate);
|
||||
var _this = this
|
||||
var playNextBuffer = function () {
|
||||
isPlaying = false;
|
||||
//console.log("~", audioBuffers.length)
|
||||
if (audioBuffers.length) {
|
||||
playAudio(audioBuffers.shift());
|
||||
}
|
||||
//if (audioBuffers.length > 1) audioBuffers.shift();
|
||||
};
|
||||
|
||||
var copyToCtxBuffer = channels > 1 ? function (fromBuffer) {
|
||||
for (var channel = 0; channel < channels; channel++) {
|
||||
var nowBuffering = audioBuffer.getChannelData(channel);
|
||||
if (resampled) {
|
||||
for (var i = 0; i < frameCount; i++) {
|
||||
nowBuffering[i * 2] = nowBuffering[i * 2 + 1] = fromBuffer[i * (channel + 1)] / 32768;
|
||||
}
|
||||
} else
|
||||
for (var i = 0; i < frameCount; i++) {
|
||||
nowBuffering[i] = fromBuffer[i * (channel + 1)] / 32768;
|
||||
}
|
||||
}
|
||||
} : function (fromBuffer) {
|
||||
var nowBuffering = audioBuffer.getChannelData(0);
|
||||
for (var i = 0; i < nowBuffering.length; i++) {
|
||||
nowBuffering[i] = fromBuffer[i] / 32768;
|
||||
}
|
||||
// nowBuffering.set(fromBuffer);
|
||||
};
|
||||
var playAudio = function (fromBuffer) {
|
||||
if (isPlaying) {
|
||||
audioBuffers.push(fromBuffer);
|
||||
//console.log(audioBuffers.length)
|
||||
return;
|
||||
}
|
||||
isPlaying = true;
|
||||
copyToCtxBuffer(fromBuffer);
|
||||
var source = context.createBufferSource();
|
||||
source.buffer = audioBuffer;
|
||||
source.connect(context.destination);
|
||||
// source.onended = playNextBuffer;
|
||||
// setTimeout(playNextBuffer, audioBufferTime-audioBuffers.length*200);
|
||||
source.start();
|
||||
if (!_this.audioInterval) {
|
||||
_this.audioInterval = setInterval(playNextBuffer, audioBuffer.duration * 1000 - 1);
|
||||
}
|
||||
};
|
||||
this.playAudio = playAudio;
|
||||
}
|
||||
/**
|
||||
* Returns true if the canvas supports WebGL
|
||||
*/
|
||||
Jessibuca.prototype.isWebGL = function () {
|
||||
return !!this.contextGL;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the GL context from the canvas element
|
||||
*/
|
||||
Jessibuca.prototype.initContextGL = function () {
|
||||
var canvas = this.canvasElement;
|
||||
var gl = null;
|
||||
|
||||
var validContextNames = ["webgl", "experimental-webgl", "moz-webgl", "webkit-3d"];
|
||||
var nameIndex = 0;
|
||||
|
||||
while (!gl && nameIndex < validContextNames.length) {
|
||||
var contextName = validContextNames[nameIndex];
|
||||
|
||||
try {
|
||||
if (this.contextOptions) {
|
||||
gl = canvas.getContext(contextName, this.contextOptions);
|
||||
} else {
|
||||
gl = canvas.getContext(contextName);
|
||||
};
|
||||
} catch (e) {
|
||||
gl = null;
|
||||
}
|
||||
|
||||
if (!gl || typeof gl.getParameter !== "function") {
|
||||
gl = null;
|
||||
}
|
||||
|
||||
++nameIndex;
|
||||
};
|
||||
|
||||
this.contextGL = gl;
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize GL shader program
|
||||
*/
|
||||
Jessibuca.prototype.initProgram = function () {
|
||||
var gl = this.contextGL;
|
||||
|
||||
var vertexShaderScript = [
|
||||
'attribute vec4 vertexPos;',
|
||||
'attribute vec4 texturePos;',
|
||||
'varying vec2 textureCoord;',
|
||||
|
||||
'void main()',
|
||||
'{',
|
||||
'gl_Position = vertexPos;',
|
||||
'textureCoord = texturePos.xy;',
|
||||
'}'
|
||||
].join('\n');
|
||||
|
||||
var fragmentShaderScript = [
|
||||
'precision highp float;',
|
||||
'varying highp vec2 textureCoord;',
|
||||
'uniform sampler2D ySampler;',
|
||||
'uniform sampler2D uSampler;',
|
||||
'uniform sampler2D vSampler;',
|
||||
'const mat4 YUV2RGB = mat4',
|
||||
'(',
|
||||
'1.1643828125, 0, 1.59602734375, -.87078515625,',
|
||||
'1.1643828125, -.39176171875, -.81296875, .52959375,',
|
||||
'1.1643828125, 2.017234375, 0, -1.081390625,',
|
||||
'0, 0, 0, 1',
|
||||
');',
|
||||
|
||||
'void main(void) {',
|
||||
'highp float y = texture2D(ySampler, textureCoord).r;',
|
||||
'highp float u = texture2D(uSampler, textureCoord).r;',
|
||||
'highp float v = texture2D(vSampler, textureCoord).r;',
|
||||
'gl_FragColor = vec4(y, u, v, 1) * YUV2RGB;',
|
||||
'}'
|
||||
].join('\n');
|
||||
|
||||
var vertexShader = gl.createShader(gl.VERTEX_SHADER);
|
||||
gl.shaderSource(vertexShader, vertexShaderScript);
|
||||
gl.compileShader(vertexShader);
|
||||
if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
|
||||
console.log('Vertex shader failed to compile: ' + gl.getShaderInfoLog(vertexShader));
|
||||
}
|
||||
|
||||
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
|
||||
gl.shaderSource(fragmentShader, fragmentShaderScript);
|
||||
gl.compileShader(fragmentShader);
|
||||
if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
|
||||
console.log('Fragment shader failed to compile: ' + gl.getShaderInfoLog(fragmentShader));
|
||||
}
|
||||
|
||||
var program = gl.createProgram();
|
||||
gl.attachShader(program, vertexShader);
|
||||
gl.attachShader(program, fragmentShader);
|
||||
gl.linkProgram(program);
|
||||
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
||||
console.log('Program failed to compile: ' + gl.getProgramInfoLog(program));
|
||||
}
|
||||
|
||||
gl.useProgram(program);
|
||||
|
||||
this.shaderProgram = program;
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize vertex buffers and attach to shader program
|
||||
*/
|
||||
Jessibuca.prototype.initBuffers = function () {
|
||||
var gl = this.contextGL;
|
||||
var program = this.shaderProgram;
|
||||
|
||||
var vertexPosBuffer = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, vertexPosBuffer);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1, 1, -1, 1, 1, -1, -1, -1]), gl.STATIC_DRAW);
|
||||
|
||||
var vertexPosRef = gl.getAttribLocation(program, 'vertexPos');
|
||||
gl.enableVertexAttribArray(vertexPosRef);
|
||||
gl.vertexAttribPointer(vertexPosRef, 2, gl.FLOAT, false, 0, 0);
|
||||
|
||||
var texturePosBuffer = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, texturePosBuffer);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1, 0, 0, 0, 1, 1, 0, 1]), gl.STATIC_DRAW);
|
||||
|
||||
var texturePosRef = gl.getAttribLocation(program, 'texturePos');
|
||||
gl.enableVertexAttribArray(texturePosRef);
|
||||
gl.vertexAttribPointer(texturePosRef, 2, gl.FLOAT, false, 0, 0);
|
||||
|
||||
this.texturePosBuffer = texturePosBuffer;
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize GL textures and attach to shader program
|
||||
*/
|
||||
Jessibuca.prototype.initTextures = function () {
|
||||
var gl = this.contextGL;
|
||||
var program = this.shaderProgram;
|
||||
|
||||
var yTextureRef = this.initTexture();
|
||||
var ySamplerRef = gl.getUniformLocation(program, 'ySampler');
|
||||
gl.uniform1i(ySamplerRef, 0);
|
||||
this.yTextureRef = yTextureRef;
|
||||
|
||||
var uTextureRef = this.initTexture();
|
||||
var uSamplerRef = gl.getUniformLocation(program, 'uSampler');
|
||||
gl.uniform1i(uSamplerRef, 1);
|
||||
this.uTextureRef = uTextureRef;
|
||||
|
||||
var vTextureRef = this.initTexture();
|
||||
var vSamplerRef = gl.getUniformLocation(program, 'vSampler');
|
||||
gl.uniform1i(vSamplerRef, 2);
|
||||
this.vTextureRef = vTextureRef;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create and configure a single texture
|
||||
*/
|
||||
Jessibuca.prototype.initTexture = function () {
|
||||
var gl = this.contextGL;
|
||||
|
||||
var textureRef = gl.createTexture();
|
||||
gl.bindTexture(gl.TEXTURE_2D, textureRef);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||
gl.bindTexture(gl.TEXTURE_2D, null);
|
||||
|
||||
return textureRef;
|
||||
};
|
||||
|
||||
/**
|
||||
* Draw picture data to the canvas.
|
||||
* If this object is using WebGL, the data must be an I420 formatted ArrayBuffer,
|
||||
* Otherwise, data must be an RGBA formatted ArrayBuffer.
|
||||
*/
|
||||
Jessibuca.prototype.drawNextOutputPicture = function (width, height, croppingParams, data) {
|
||||
var gl = this.contextGL;
|
||||
if (gl) {
|
||||
this.drawNextOuptutPictureGL(width, height, croppingParams, data);
|
||||
} else {
|
||||
this.drawNextOuptutPictureRGBA(width, height, croppingParams, data);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Draw the next output picture using WebGL
|
||||
*/
|
||||
Jessibuca.prototype.drawNextOuptutPictureGL = function (width, height, croppingParams, data) {
|
||||
var gl = this.contextGL;
|
||||
var texturePosBuffer = this.texturePosBuffer;
|
||||
var yTextureRef = this.yTextureRef;
|
||||
var uTextureRef = this.uTextureRef;
|
||||
var vTextureRef = this.vTextureRef;
|
||||
this.contextGL.viewport(0, 0, this.canvasElement.width, this.canvasElement.height);
|
||||
// if (!croppingParams) {
|
||||
// gl.viewport(0, 0, width, height);
|
||||
// } else {
|
||||
// gl.viewport(0, 0, croppingParams.width, croppingParams.height);
|
||||
|
||||
// var tTop = croppingParams.top / height;
|
||||
// var tLeft = croppingParams.left / width;
|
||||
// var tBottom = croppingParams.height / height;
|
||||
// var tRight = croppingParams.width / width;
|
||||
// var texturePosValues = new Float32Array([tRight, tTop, tLeft, tTop, tRight, tBottom, tLeft, tBottom]);
|
||||
|
||||
// gl.bindBuffer(gl.ARRAY_BUFFER, texturePosBuffer);
|
||||
// gl.bufferData(gl.ARRAY_BUFFER, texturePosValues, gl.DYNAMIC_DRAW);
|
||||
// }
|
||||
gl.activeTexture(gl.TEXTURE0);
|
||||
gl.bindTexture(gl.TEXTURE_2D, yTextureRef);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, width, height, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, data[0]);
|
||||
|
||||
gl.activeTexture(gl.TEXTURE1);
|
||||
gl.bindTexture(gl.TEXTURE_2D, uTextureRef);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, width / 2, height / 2, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, data[1]);
|
||||
|
||||
gl.activeTexture(gl.TEXTURE2);
|
||||
gl.bindTexture(gl.TEXTURE_2D, vTextureRef);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, width / 2, height / 2, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, data[2]);
|
||||
|
||||
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
||||
};
|
||||
|
||||
/**
|
||||
* Draw next output picture using ARGB data on a 2d canvas.
|
||||
*/
|
||||
Jessibuca.prototype.drawNextOuptutPictureRGBA = function (width, height, croppingParams, data) {
|
||||
// var canvas = this.canvasElement;
|
||||
//var argbData = data;
|
||||
//var ctx = canvas.getContext('2d');
|
||||
// var imageData = ctx.getImageData(0, 0, width, height);
|
||||
//this.imageData = this.ctx2d.getImageData(0, 0, width, height);
|
||||
this.imageData.data.set(data);
|
||||
//Module.print(typeof this.imageData.data);
|
||||
if (!croppingParams) {
|
||||
this.ctx2d.putImageData(this.imageData, 0, 0);
|
||||
} else {
|
||||
this.ctx2d.putImageData(this.imageData, -croppingParams.left, -croppingParams.top, 0, 0, croppingParams.width, croppingParams.height);
|
||||
}
|
||||
};
|
||||
Jessibuca.prototype.ctx2d = null;
|
||||
Jessibuca.prototype.imageData = null;
|
||||
Jessibuca.prototype.initRGB = function (width, height) {
|
||||
this.ctx2d = this.canvasElement.getContext('2d');
|
||||
this.imageData = this.ctx2d.getImageData(0, 0, width, height);
|
||||
this.clear = function () {
|
||||
this.ctx2d.clearRect(0, 0, width, height)
|
||||
};
|
||||
//Module.print(this.imageData);
|
||||
};
|
||||
Jessibuca.prototype.close = function () {
|
||||
if (this.audioInterval) {
|
||||
clearInterval(this.audioInterval)
|
||||
delete this.audioInterval
|
||||
}
|
||||
delete this.playAudio
|
||||
this.decoderWorker.postMessage({ cmd: "close" })
|
||||
this.contextGL.clear(this.contextGL.COLOR_BUFFER_BIT);
|
||||
}
|
||||
Jessibuca.prototype.destroy = function () {
|
||||
this.close()
|
||||
this.decoderWorker.terminate()
|
||||
}
|
||||
Jessibuca.prototype.play = function (url) {
|
||||
this.decoderWorker.postMessage({ cmd: "play", url: url, isWebGL: this.isWebGL() })
|
||||
}
|
8
dashboard/ui/demo.html
Normal file
8
dashboard/ui/demo.html
Normal file
@@ -0,0 +1,8 @@
|
||||
<meta charset="utf-8">
|
||||
<title>plugin-jessica demo</title>
|
||||
<script src="https://unpkg.com/vue"></script>
|
||||
<script src="./plugin-jessica.js"></script>
|
||||
|
||||
|
||||
<plugin-jessica></plugin-jessica>
|
||||
|
1253
dashboard/ui/plugin-jessica.js
Normal file
1253
dashboard/ui/plugin-jessica.js
Normal file
File diff suppressed because it is too large
Load Diff
1
dashboard/ui/plugin-jessica.js.map
Normal file
1
dashboard/ui/plugin-jessica.js.map
Normal file
File diff suppressed because one or more lines are too long
2
dashboard/ui/plugin-jessica.min.js
vendored
Normal file
2
dashboard/ui/plugin-jessica.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dashboard/ui/plugin-jessica.min.js.map
Normal file
1
dashboard/ui/plugin-jessica.min.js.map
Normal file
File diff suppressed because one or more lines are too long
9
go.mod
Normal file
9
go.mod
Normal file
@@ -0,0 +1,9 @@
|
||||
module github.com/Monibuca/jessicaplugin
|
||||
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/Monibuca/engine v1.0.2
|
||||
github.com/gobwas/ws v1.0.2
|
||||
github.com/langhuihui/monibuca v0.4.1
|
||||
)
|
42
go.sum
Normal file
42
go.sum
Normal file
@@ -0,0 +1,42 @@
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Monibuca/engine v1.0.2 h1:UpPAEQVYrVQrLr9GVGcbu8x5Oiemqd5J2zjGZ/Fhg74=
|
||||
github.com/Monibuca/engine v1.0.2/go.mod h1:NjqVgtXuRSOyk3+NWgCuDf2p7TsBisjYxoEwA9uCZ38=
|
||||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=
|
||||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/elgs/gostrgen v0.0.0-20161222160715-9d61ae07eeae/go.mod h1:wruC5r2gHdr/JIUs5Rr1V45YtsAzKXZxAnn/5rPC97g=
|
||||
github.com/funny/slab v0.0.0-20180511031532-b1fad5e5d478 h1:Db9StoJ6RZN3YttC0Pm0I4Y5izITRYch3RMbT59BYN0=
|
||||
github.com/funny/slab v0.0.0-20180511031532-b1fad5e5d478/go.mod h1:0j1+svBH8ABEIPdUP0AIg4qedsybnXGJBakCEw8cfoo=
|
||||
github.com/funny/utest v0.0.0-20161029064919-43870a374500/go.mod h1:mUn39tBov9jKnTWV1RlOYoNzxdBFHiSzXWdY1FoNGGg=
|
||||
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
|
||||
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
||||
github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
|
||||
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
|
||||
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
||||
github.com/langhuihui/monibuca v0.4.1 h1:hR5xiVtYJM272ChQUrKdNd+AQyY98SNxVZEx2WAuNmA=
|
||||
github.com/langhuihui/monibuca v0.4.1/go.mod h1:S4rqYUQ+bCB3WdwuXTJ92FqVRZz5Sh7zAXOJc94JqMI=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/quangngotan95/go-m3u8 v0.1.0/go.mod h1:smzfWHlYpBATVNu1GapKLYiCtEo5JxridIgvvudZ+Wc=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/shirou/gopsutil v2.19.12+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/shirou/gopsutil v2.20.1+incompatible h1:oIq9Cq4i84Hk8uQAUOG3eNdI/29hBawGrD5YRl6JRDY=
|
||||
github.com/shirou/gopsutil v2.20.1+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/yakovlevdmv/Golang-iso8601-duration v0.0.0-20180403125811-e5db0413b903/go.mod h1:9o96byDMk+osDZqiIS2a7E7y0cWmg4rRTjQRWVHpFWE=
|
||||
github.com/yakovlevdmv/WS-Discovery v0.0.0-20180512141937-16170c6c3677/go.mod h1:/VKdrRRbAVE0pvkoPTUlfXw1zxqEpflVsgF25aR5gbk=
|
||||
github.com/yakovlevdmv/goonvif v0.0.0-20180517145634-8181eb3ef2fb/go.mod h1:Os0AToR0I28wSLpS4rQtZdMEcfGKJcSrTaJughAopv4=
|
||||
github.com/yakovlevdmv/gosoap v0.0.0-20180512142237-299a954b1c6d/go.mod h1:NhCpqPG+N2wrLSqEHVG3FKl4uAPvtFHUx7IlCVpW1PU=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/net v0.0.0-20200226051749-491c5fce7268/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
47
main.go
Normal file
47
main.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package jessica
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"mime"
|
||||
"net/http"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
. "github.com/Monibuca/engine"
|
||||
)
|
||||
|
||||
var config = new(ListenerConfig)
|
||||
var publicPath string
|
||||
|
||||
func init() {
|
||||
_, currentFilePath, _, _ := runtime.Caller(0)
|
||||
publicPath = path.Join(path.Dir(currentFilePath), "dashboard", "public")
|
||||
InstallPlugin(&PluginConfig{
|
||||
Name: "Jessica",
|
||||
Type: PLUGIN_SUBSCRIBER,
|
||||
Config: config,
|
||||
UI: path.Join(path.Dir(currentFilePath), "./dashboard/ui/plugin-jessica.min.js"),
|
||||
Version: "1.0.0",
|
||||
Run: run,
|
||||
})
|
||||
}
|
||||
func run() {
|
||||
log.Printf("server Jessica start at %s", config.ListenAddr)
|
||||
http.HandleFunc("/jessibuca/", jessibuca)
|
||||
log.Fatal(http.ListenAndServe(config.ListenAddr, http.HandlerFunc(WsHandler)))
|
||||
}
|
||||
func jessibuca(w http.ResponseWriter, r *http.Request) {
|
||||
filePath := strings.TrimPrefix(r.URL.Path, "/jessibuca")
|
||||
if mime := mime.TypeByExtension(path.Ext(filePath)); mime != "" {
|
||||
w.Header().Set("Content-Type", mime)
|
||||
}
|
||||
if f, err := ioutil.ReadFile(publicPath + filePath); err == nil {
|
||||
if _, err = w.Write(f); err != nil {
|
||||
w.WriteHeader(500)
|
||||
}
|
||||
} else {
|
||||
w.WriteHeader(404)
|
||||
}
|
||||
}
|
76
subscriber.go
Normal file
76
subscriber.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package jessica
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
. "github.com/Monibuca/engine"
|
||||
"github.com/Monibuca/engine/avformat"
|
||||
"github.com/Monibuca/engine/pool"
|
||||
"github.com/gobwas/ws"
|
||||
)
|
||||
|
||||
func WsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
sign := r.URL.Query().Get("sign")
|
||||
isFlv := false
|
||||
if err := AuthHooks.Trigger(sign); err != nil {
|
||||
w.WriteHeader(403)
|
||||
return
|
||||
}
|
||||
streamPath := strings.TrimLeft(r.RequestURI, "/")
|
||||
if strings.HasSuffix(streamPath, ".flv") {
|
||||
streamPath = strings.TrimRight(streamPath, ".flv")
|
||||
isFlv = true
|
||||
}
|
||||
conn, _, _, err := ws.UpgradeHTTP(r, w)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
baseStream := OutputStream{Sign: sign}
|
||||
baseStream.ID = conn.RemoteAddr().String()
|
||||
defer conn.Close()
|
||||
if isFlv {
|
||||
baseStream.Type = "JessicaFlv"
|
||||
baseStream.SendHandler = func(packet *avformat.SendPacket) error {
|
||||
return avformat.WriteFLVTag(conn, packet)
|
||||
}
|
||||
if err := ws.WriteHeader(conn, ws.Header{
|
||||
Fin: true,
|
||||
OpCode: ws.OpBinary,
|
||||
Length: int64(13),
|
||||
}); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = conn.Write(avformat.FLVHeader); err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
baseStream.Type = "Jessica"
|
||||
baseStream.SendHandler = func(packet *avformat.SendPacket) error {
|
||||
err := ws.WriteHeader(conn, ws.Header{
|
||||
Fin: true,
|
||||
OpCode: ws.OpBinary,
|
||||
Length: int64(len(packet.Packet.Payload) + 5),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
head := pool.GetSlice(5)
|
||||
head[0] = packet.Packet.Type - 7
|
||||
binary.BigEndian.PutUint32(head[1:5], packet.Timestamp)
|
||||
if _, err = conn.Write(head); err != nil {
|
||||
return err
|
||||
}
|
||||
pool.RecycleSlice(head)
|
||||
//if p.Header[0] == 2 {
|
||||
// fmt.Printf("%6d %X\n", (uint32(p.Packet.Payload[5])<<24)|(uint32(p.Packet.Payload[6])<<16)|(uint32(p.Packet.Payload[7])<<8)|uint32(p.Packet.Payload[8]), p.Packet.Payload[9])
|
||||
//}
|
||||
if _, err = conn.Write(packet.Packet.Payload); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
baseStream.Play(streamPath)
|
||||
}
|
Reference in New Issue
Block a user