mirror of
https://github.com/flavioribeiro/donut.git
synced 2025-10-06 15:36:50 +08:00
embed demo player
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -17,3 +17,5 @@ donut
|
|||||||
tags
|
tags
|
||||||
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
.vscode
|
@@ -1,10 +1,18 @@
|
|||||||
|
# Moving player to static
|
||||||
|
|
||||||
|
## Date: 2/3/24
|
||||||
|
### Hypothesis: It's easy to do experimentation with local player
|
||||||
|
### Summary
|
||||||
|
|
||||||
|
Copy and adapt code from https://github.com/flavioribeiro/donut-video into static/demo/
|
||||||
|
|
||||||
# Investigating a potential memory leak
|
# Investigating a potential memory leak
|
||||||
|
|
||||||
## Date: 2/2/24
|
## Date: 2/2/24
|
||||||
### Hyphotesis: There's a memory leak happening
|
### Hypothesis: There's a memory leak happening
|
||||||
### Signs: pprof/allocs
|
### Signs: pprof/allocs
|
||||||

|

|
||||||
### Sumary
|
### Summary
|
||||||
|
|
||||||
* Start the donut `make run`,
|
* Start the donut `make run`,
|
||||||
* Check the general profiling http://localhost:6060/debug/pprof/?debug=1
|
* Check the general profiling http://localhost:6060/debug/pprof/?debug=1
|
@@ -26,7 +26,7 @@ func NewStreamingController(c *entities.Config, l *zap.SugaredLogger) *Streaming
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *StreamingController) Stream(sp entities.StreamParameters) {
|
func (c *StreamingController) Stream(sp *entities.StreamParameters) {
|
||||||
r, w := io.Pipe()
|
r, w := io.Pipe()
|
||||||
|
|
||||||
defer r.Close()
|
defer r.Close()
|
||||||
@@ -78,7 +78,7 @@ func (c *StreamingController) Stream(sp entities.StreamParameters) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *StreamingController) writeMpegtsToWebRTC(mpegTSDemuxData *astits.DemuxerData, h264PID uint16, err error, sp entities.StreamParameters, eia608Reader *EIA608Reader) error {
|
func (c *StreamingController) writeMpegtsToWebRTC(mpegTSDemuxData *astits.DemuxerData, h264PID uint16, err error, sp *entities.StreamParameters, eia608Reader *EIA608Reader) error {
|
||||||
if mpegTSDemuxData.PID == h264PID && mpegTSDemuxData.PES != nil {
|
if mpegTSDemuxData.PID == h264PID && mpegTSDemuxData.PES != nil {
|
||||||
|
|
||||||
if err = sp.VideoTrack.WriteSample(media.Sample{Data: mpegTSDemuxData.PES.Data, Duration: time.Second / 30}); err != nil {
|
if err = sp.VideoTrack.WriteSample(media.Sample{Data: mpegTSDemuxData.PES.Data, Duration: time.Second / 30}); err != nil {
|
||||||
|
@@ -1,67 +0,0 @@
|
|||||||
/* eslint-env browser */
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
const pc = new RTCPeerConnection({
|
|
||||||
iceServers: [{
|
|
||||||
urls: 'stun:stun.l.google.com:19302'
|
|
||||||
}]
|
|
||||||
})
|
|
||||||
const log = msg => {
|
|
||||||
document.getElementById('div').innerHTML += msg + '<br>'
|
|
||||||
}
|
|
||||||
|
|
||||||
pc.ontrack = function (event) {
|
|
||||||
const el = document.createElement(event.track.kind)
|
|
||||||
el.srcObject = event.streams[0]
|
|
||||||
el.autoplay = true
|
|
||||||
el.controls = true
|
|
||||||
|
|
||||||
document.getElementById('remoteVideos').appendChild(el)
|
|
||||||
}
|
|
||||||
|
|
||||||
pc.oniceconnectionstatechange = e => log(pc.iceConnectionState)
|
|
||||||
pc.onicecandidate = event => {
|
|
||||||
if (event.candidate === null) {
|
|
||||||
document.getElementById('localSessionDescription').value = btoa(JSON.stringify(pc.localDescription))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Offer to receive 1 audio, and 1 video track
|
|
||||||
pc.addTransceiver('video', {
|
|
||||||
direction: 'sendrecv'
|
|
||||||
})
|
|
||||||
pc.addTransceiver('audio', {
|
|
||||||
direction: 'sendrecv'
|
|
||||||
})
|
|
||||||
|
|
||||||
pc.createOffer().then(d => pc.setLocalDescription(d)).catch(log)
|
|
||||||
|
|
||||||
window.startSession = () => {
|
|
||||||
const sd = document.getElementById('remoteSessionDescription').value
|
|
||||||
if (sd === '') {
|
|
||||||
return alert('Session Description must not be empty')
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
pc.setRemoteDescription(JSON.parse(atob(sd)))
|
|
||||||
} catch (e) {
|
|
||||||
alert(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.copySessionDescription = () => {
|
|
||||||
const browserSessionDescription = document.getElementById('localSessionDescription')
|
|
||||||
|
|
||||||
browserSessionDescription.focus()
|
|
||||||
browserSessionDescription.select()
|
|
||||||
|
|
||||||
try {
|
|
||||||
const successful = document.execCommand('copy')
|
|
||||||
const msg = successful ? 'successful' : 'unsuccessful'
|
|
||||||
log('Copying SessionDescription was ' + msg)
|
|
||||||
} catch (err) {
|
|
||||||
log('Oops, unable to copy SessionDescription ' + err)
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,30 +0,0 @@
|
|||||||
<!--
|
|
||||||
SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
|
||||||
SPDX-License-Identifier: MIT
|
|
||||||
-->
|
|
||||||
Browser Session Description
|
|
||||||
<br/>
|
|
||||||
<textarea id="localSessionDescription" readonly="true"></textarea>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<button onclick="window.copySessionDescription()">Copy browser Session Description to clipboard</button>
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
Remote Session Description
|
|
||||||
<br/>
|
|
||||||
<textarea id="remoteSessionDescription"></textarea>
|
|
||||||
<br/>
|
|
||||||
<button onclick="window.startSession()">Start Session</button>
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
Video
|
|
||||||
<br/>
|
|
||||||
<div id="remoteVideos"></div> <br />
|
|
||||||
|
|
||||||
Logs
|
|
||||||
<br/>
|
|
||||||
<div id="div"></div>
|
|
@@ -1 +0,0 @@
|
|||||||
Initially copied from https://github.com/pion/webrtc/tree/master/examples/play-from-disk/jsfiddle
|
|
@@ -81,7 +81,7 @@ func (h *SignalingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) err
|
|||||||
|
|
||||||
metadataSender, err := h.webRTCController.CreateDataChannel(peer, entities.MetadataChannelID)
|
metadataSender, err := h.webRTCController.CreateDataChannel(peer, entities.MetadataChannelID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.l.Errorw("error while createing a web rtc data channel",
|
h.l.Errorw("error while creating a web rtc data channel",
|
||||||
"error", err,
|
"error", err,
|
||||||
)
|
)
|
||||||
return err
|
return err
|
||||||
@@ -110,7 +110,7 @@ func (h *SignalingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) err
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
go h.streamingController.Stream(entities.StreamParameters{
|
go h.streamingController.Stream(&entities.StreamParameters{
|
||||||
Cancel: cancel,
|
Cancel: cancel,
|
||||||
Ctx: ctx,
|
Ctx: ctx,
|
||||||
WebRTCConn: peer,
|
WebRTCConn: peer,
|
||||||
|
@@ -20,8 +20,11 @@ func NewServeMux(
|
|||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
mux.Handle("/", index)
|
mux.Handle("/", index)
|
||||||
|
|
||||||
|
fs := http.FileServer(http.Dir("./static"))
|
||||||
|
mux.Handle("/demo/", http.StripPrefix("/demo/", fs))
|
||||||
|
|
||||||
mux.Handle("/doSignaling", setCors(errorHandler(l, signaling)))
|
mux.Handle("/doSignaling", setCors(errorHandler(l, signaling)))
|
||||||
mux.Handle("/demo", http.FileServer(http.Dir("./demo")))
|
|
||||||
|
|
||||||
return mux
|
return mux
|
||||||
}
|
}
|
||||||
|
@@ -3,4 +3,5 @@ ffmpeg -hide_banner -loglevel verbose \
|
|||||||
-f lavfi -i sine=frequency=1000:sample_rate=44100 \
|
-f lavfi -i sine=frequency=1000:sample_rate=44100 \
|
||||||
-c:v libx264 -preset veryfast -tune zerolatency -profile:v baseline \
|
-c:v libx264 -preset veryfast -tune zerolatency -profile:v baseline \
|
||||||
-b:v 1000k -bufsize 2000k -x264opts keyint=30:min-keyint=30:scenecut=-1 \
|
-b:v 1000k -bufsize 2000k -x264opts keyint=30:min-keyint=30:scenecut=-1 \
|
||||||
|
-c:a aac -b:a 128k \
|
||||||
-f mpegts "udp://${SRT_INPUT_HOST}:${SRT_INPUT_PORT}?pkt_size=${PKT_SIZE}"
|
-f mpegts "udp://${SRT_INPUT_HOST}:${SRT_INPUT_PORT}?pkt_size=${PKT_SIZE}"
|
133
static/demo.js
Normal file
133
static/demo.js
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
// just to avoid adding dup messages
|
||||||
|
window.metadataMessages = {}
|
||||||
|
|
||||||
|
window.startSession = () => {
|
||||||
|
let srtHost = document.getElementById('srt-host').value;
|
||||||
|
let srtPort = document.getElementById('srt-port').value;
|
||||||
|
let srtStreamId = document.getElementById('srt-stream-id').value;
|
||||||
|
|
||||||
|
setupWebRTC((pc, offer) => {
|
||||||
|
let srtFullAddress = JSON.stringify({
|
||||||
|
"srtHost": srtHost,
|
||||||
|
"srtPort": srtPort,
|
||||||
|
"srtStreamId": srtStreamId,
|
||||||
|
offer
|
||||||
|
});
|
||||||
|
|
||||||
|
// sending localSDP,SRT params, and fetching remote SDP
|
||||||
|
fetchRemoteDescription(srtFullAddress).then(remoteOffer => {
|
||||||
|
log("receiving remote sdp offer: " + JSON.stringify(remoteOffer));
|
||||||
|
|
||||||
|
if (remoteOffer === undefined) {
|
||||||
|
log("error while fetching remote");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pc.setRemoteDescription(remoteOffer);
|
||||||
|
}).catch(log, "error");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const setupWebRTC = (setRemoteSDPfn) => {
|
||||||
|
log("setting up web rtc");
|
||||||
|
const pc = new RTCPeerConnection({
|
||||||
|
iceServers: [{
|
||||||
|
urls: 'stun:stun.l.google.com:19302'
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
// offer to (only) receive 1 audio, and 1 video track
|
||||||
|
pc.addTransceiver('video', {
|
||||||
|
direction: 'recvonly'
|
||||||
|
});
|
||||||
|
pc.addTransceiver('audio', {
|
||||||
|
direction: 'recvonly'
|
||||||
|
});
|
||||||
|
|
||||||
|
// once a track arrives, add it to the remoteVideos div
|
||||||
|
// with auto play.
|
||||||
|
pc.ontrack = function (event) {
|
||||||
|
const el = document.createElement(event.track.kind);
|
||||||
|
el.srcObject = event.streams[0];
|
||||||
|
el.autoplay = true
|
||||||
|
el.controls = true;
|
||||||
|
el.width = "640";
|
||||||
|
el.height = "360";
|
||||||
|
|
||||||
|
document.getElementById('remoteVideos').appendChild(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
pc.createDataChannel('metadata');
|
||||||
|
// once the metadata arrives, add it to the metadata div
|
||||||
|
pc.ondatachannel = (e) => {
|
||||||
|
log("ondatachannel: " + e);
|
||||||
|
|
||||||
|
e.channel.onmessage = (event) => {
|
||||||
|
let msg = JSON.parse(event.data)
|
||||||
|
if (msg.Message in metadataMessages) {
|
||||||
|
// avoid logging dup messages
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const el = document.createElement("p")
|
||||||
|
el.innerText = msg.Type.padEnd(8, ' ') + ": " + msg.Message
|
||||||
|
|
||||||
|
let metadata = document.getElementById('metadata');
|
||||||
|
metadata.insertBefore(el, metadata.firstChild);
|
||||||
|
metadataMessages[msg.Message] = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
pc.oniceconnectionstatechange = e => log("ice state change: " + pc.iceConnectionState);
|
||||||
|
pc.onicegatheringstatechange = e => log("gathering state change: " + pc.iceGatheringState);
|
||||||
|
pc.onsignalingstatechange = e => log("signaling state change: " + pc.signalingState);
|
||||||
|
|
||||||
|
// creating a local sdp offer
|
||||||
|
pc.createOffer()
|
||||||
|
.then(offer => {
|
||||||
|
pc.setLocalDescription(offer);
|
||||||
|
setRemoteSDPfn(pc, offer);
|
||||||
|
}).catch(log, "error");
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchRemoteDescription = async (bodyRequest) => {
|
||||||
|
log("requesting remote sdp offer for: " + bodyRequest)
|
||||||
|
|
||||||
|
const res = await fetch('/doSignaling', {
|
||||||
|
method: 'post',
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json, text/plain, */*',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: bodyRequest
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.status !== 200) {
|
||||||
|
res.text().then(err => {
|
||||||
|
log(err, "error");
|
||||||
|
window.alert(err);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
const formattedNow = () => {
|
||||||
|
let now = new Date();
|
||||||
|
let minutes = now.getMinutes().toString().padStart(2, '0');
|
||||||
|
let seconds = now.getSeconds().toString().padStart(2, '0');
|
||||||
|
let ms = now.getMilliseconds().toString().padStart(3, '0');
|
||||||
|
return minutes + ":" + seconds + ":" + ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
const log = (msg, level = "info") => {
|
||||||
|
const el = document.createElement("p")
|
||||||
|
if (level === "error") {
|
||||||
|
el.style = "color: red;background-color: yellow;";
|
||||||
|
}
|
||||||
|
|
||||||
|
el.innerText = "[[" + level.toUpperCase().padEnd(5, ' ') + "]] " + formattedNow() + " : " + msg
|
||||||
|
|
||||||
|
let logEl = document.getElementById('log');
|
||||||
|
logEl.insertBefore(el, logEl.firstChild);
|
||||||
|
}
|
66
static/index.html
Normal file
66
static/index.html
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>donut</title>
|
||||||
|
<script src="demo.js"></script>
|
||||||
|
<style src="demo.css"></style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<h1>SRT Config</h1>
|
||||||
|
<b> SRT Host </b>
|
||||||
|
<input type="text" id="srt-host" value="srt"> <br />
|
||||||
|
|
||||||
|
<b> SRT Port </b>
|
||||||
|
<input type="text" id="srt-port" value="40052" /> <br />
|
||||||
|
|
||||||
|
<b> SRT Stream ID </b>
|
||||||
|
<input type="text" id="srt-stream-id" value="stream-id" /> <br />
|
||||||
|
<button onclick="onConnect()"> Connect </button>
|
||||||
|
|
||||||
|
<h1>Video</h1>
|
||||||
|
<div id="remoteVideos"></div>
|
||||||
|
|
||||||
|
<h1>Metadata</h1>
|
||||||
|
<div id="metadata"></div>
|
||||||
|
|
||||||
|
<h1>Logs</h1>
|
||||||
|
<div id="log"></div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function docReady(fn) {
|
||||||
|
if (document.readyState === "complete" || document.readyState === "interactive") {
|
||||||
|
setTimeout(fn, 1);
|
||||||
|
} else {
|
||||||
|
document.addEventListener("DOMContentLoaded", fn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
docReady(function () {
|
||||||
|
const queryString = window.location.search;
|
||||||
|
const urlParams = new URLSearchParams(queryString);
|
||||||
|
|
||||||
|
window.onConnect = () => {
|
||||||
|
window.startSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (urlParams.has('srtHost')) {
|
||||||
|
document.getElementById('srt-host').value = urlParams.get('srtHost');
|
||||||
|
}
|
||||||
|
if (urlParams.has('srtPort')) {
|
||||||
|
document.getElementById('srt-port').value = urlParams.get('srtPort');
|
||||||
|
}
|
||||||
|
if (urlParams.has('srtStreamId')) {
|
||||||
|
document.getElementById('srt-stream-id').value = urlParams.get('srtStreamId');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (urlParams.get('autoplay') === "true") {
|
||||||
|
onConnect();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</html>
|
Reference in New Issue
Block a user