Skip to content

Commit

Permalink
implement streaming with disco xep
Browse files Browse the repository at this point in the history
  • Loading branch information
deleolajide committed Jan 3, 2025
1 parent df9dab0 commit 8ca22d7
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 93 deletions.
82 changes: 43 additions & 39 deletions classes/jsp/audio-watcher.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@
</head>

<body>
<b> Stream Key </b> <input type="text" id="streamKey" /> <br />
<button onclick="window.watchStream()"> Watch Stream </button>

<h3> Audio </h3>
<audio id="audioPlayer" autoplay controls style="width: 500"> </audio>

Expand All @@ -21,7 +18,7 @@ <h3> Connection State </h3>
</body>

<script>
window.watchStream = () => {
window.addEventListener("load", function() {
window.connection = new Strophe.Connection(location.protocol.replace("http", "ws") + "//" + location.host + "/ws/");

window.connection.connect(location.hostname, null, function (status) {
Expand All @@ -30,48 +27,55 @@ <h3> Connection State </h3>
if (status === Strophe.Status.CONNECTED) {
window.connection.send($pres());

setTimeout(() => {
const streamKey = document.getElementById('streamKey').value

if (streamKey === '') {
return window.alert('Stream Key must not be empty')
}

let peerConnection = new RTCPeerConnection()
peerConnection.addTransceiver('audio', { direction: 'recvonly' })

peerConnection.ontrack = function (event) {
document.getElementById('audioPlayer').srcObject = event.streams[0]
}

peerConnection.oniceconnectionstatechange = () => {
document.getElementById('connectionState').innerText = peerConnection.iceConnectionState;
}

peerConnection.createOffer().then(offer => {
peerConnection.setLocalDescription(offer);
console.debug('whep offer', offer.sdp);
window.connection.sendIQ($iq({type: 'get', to: window.connection.domain}).c('whep', {xmlns: 'urn:xmpp:whep:0'}),
function (res) {
console.debug('whep get response', res);
const item = res.querySelector('item');

window.connection.sendIQ($iq({type: 'set', to: window.connection.domain}).c('whep', {id: streamKey, xmlns: 'urn:xmpp:whep:0'}).c('sdp', offer.sdp),
function (res) {
console.debug('whep response', res);
const answer = res.querySelector('sdp').innerHTML;
peerConnection.setRemoteDescription({sdp: answer, type: 'answer'});
console.debug('whep answer', answer);

}, function (err) {
console.warn('whep failed', err);
}
);
})
});
if (item) {
viewStream(item.getAttribute("id"));
}
}, function (err) {
console.warn('whep failed', err);
}
);
}
else

if (status === Strophe.Status.DISCONNECTED) {

}
});
}
});

function viewStream(streamKey) {
let peerConnection = new RTCPeerConnection()
peerConnection.addTransceiver('audio', { direction: 'recvonly' })

peerConnection.ontrack = function (event) {
document.getElementById('audioPlayer').srcObject = event.streams[0]
}

peerConnection.oniceconnectionstatechange = () => {
document.getElementById('connectionState').innerText = peerConnection.iceConnectionState;
}

peerConnection.createOffer().then(offer => {
peerConnection.setLocalDescription(offer);
console.debug('whep offer', offer.sdp);

window.connection.sendIQ($iq({type: 'set', to: window.connection.domain}).c('whep', {id: streamKey, xmlns: 'urn:xmpp:whep:0'}).c('sdp', offer.sdp),
function (res) {
console.debug('whep set response', res);
const answer = res.querySelector('sdp').innerHTML;
peerConnection.setRemoteDescription({sdp: answer, type: 'answer'});
console.debug('whep answer', answer);

}, function (err) {
console.warn('whep failed', err);
}
);
})
}
</script>
</html>
9 changes: 6 additions & 3 deletions classes/jsp/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<input style="display:none" id="load-midifile" type="file" />
<table width="100%">
<tr>
<td><img height=96 src="./assets/icon_128.png"/></td>
<td><img height=64 src="./assets/icon_128.png"/></td>
<td>
<div><center>
<fluent-button appearance="accent" title='Toggle play/stop' id="play">On</fluent-button>
Expand Down Expand Up @@ -57,7 +57,8 @@
<iframe src="https://jus-be.github.io/chordpro-pdf-online/" style="display: none;" id="chordpro"></iframe>

<table id="settings" width="90%">
<tr><td><b>Guitar Strum</b></td><td><select id="guitarType" type="text" class="form-control input"></select></td>
<tr><td><b>Guitar Strum</b></td>
<td><select id="guitarType" type="text" class="form-control input"></select></td>
<td><select title='Strum type for Pad 3'id="guitarStrum1" type="text" class="form-control input"></select></td>
<td><select title='Strum type for Pad 4'id="guitarStrum2" type="text" class="form-control input"></select></td>
<td><select title='Strum type for Pad 5' id="guitarStrum3" type="text" class="form-control input"></select></td>
Expand Down Expand Up @@ -106,6 +107,7 @@
</td>
</tr>
<tr><td><b>Song Sequence</b></td><td><select id="songSequence" type="text" class="form-control input"></select></td><td></td></tr>
<tr id="active_streams" style="display:none"><td><b>Active Streams</b></td><td colspan=7><select id="activeStreams" type="text" class="form-control input"></select></td></tr>
</table>
</div>
<div style="margin-top: 20px;">
Expand Down Expand Up @@ -158,6 +160,7 @@
<script src="./js/sf2.synth.min.js"></script>
<script src="./js/midi.ble.js"></script>
<script src="./js/midi-creator.js"></script>
<script src="./js/main.js"></script>
<script src="./js/main.js"></script>
<audio id="audioPlayer" autoplay style="display: none"> </audio>
</body>
</html>
97 changes: 85 additions & 12 deletions classes/jsp/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ const CONTROL = 100;

var recorderDestination = null;
var streamDestination = null;
var peerConnection = null;
var publishConnection = null;
var watchConnection = null;
var lavaGenieWaitout = 1000;
var keysSound1 = null;
var keysSound2 = null;
Expand Down Expand Up @@ -937,6 +938,74 @@ function packString(str) {
return bArr;
}

function fetchStreams() {
console.debug("fetchStreams");

const streamSong = document.querySelector("#stream_song");
const activeStreams = document.querySelector("#activeStreams");
activeStreams.options[0] = new Option("**UNUSED**", "activeStreams", false, false);

activeStreams.addEventListener("change", function() {
console.debug("fetchStreams selected stream", activeStreams.value);

if ("activeStreams" == activeStreams.value) {
if (watchConnection) {
watchConnection.close();
watchConnection = null;

const streamStarted = streamSong.innerText == "Stop Stream";
streamSong.style.setProperty("--accent-fill-rest", streamStarted ? "red" : "green");
}

} else {
watchConnection = new RTCPeerConnection()
watchConnection.addTransceiver('audio', { direction: 'recvonly' })

watchConnection.ontrack = function (event) {
document.getElementById('audioPlayer').srcObject = event.streams[0]
}

watchConnection.oniceconnectionstatechange = () => {
console.debug("", watchConnection.iceConnectionState);
streamSong.style.setProperty("--accent-fill-rest", "purple");
}

watchConnection.createOffer().then(offer => {
watchConnection.setLocalDescription(offer);
console.debug('fetchStreams offer', offer.sdp);

window.connection.sendIQ($iq({type: 'set', to: window.connection.domain}).c('whep', {id: activeStreams.value, xmlns: 'urn:xmpp:whep:0'}).c('sdp', offer.sdp),
function (res) {
console.debug('fetchStreams whep set response', res);
const answer = res.querySelector('sdp').innerHTML;
watchConnection.setRemoteDescription({sdp: answer, type: 'answer'});
console.debug('fetchStreams whep answer', answer);

}, function (err) {
console.warn('fetchStreams whep failed', err);
}
);
})
}
});

window.connection.sendIQ($iq({type: 'get', to: window.connection.domain}).c('whep', {xmlns: 'urn:xmpp:whep:0'}),
function (res) {
console.debug('fetchStreams response', res);
const items = res.querySelectorAll('item');
let count = 1;

for (item of items) {
const id = item.getAttribute("id");
activeStreams.options[count] = new Option(id, id, false, false);
}

}, function (err) {
console.warn('fetchStreams failed', err);
}
);
}

async function handleMediaStream(started) {
console.debug("handleMediaStream", started);

Expand All @@ -949,27 +1018,27 @@ async function handleMediaStream(started) {

let mediaOptions = {audio: true, video: false}

peerConnection = new RTCPeerConnection();
publishConnection = new RTCPeerConnection();

peerConnection.oniceconnectionstatechange = () => {
console.debug("handleMediaStream - status", peerConnection.iceConnectionState);
publishConnection.oniceconnectionstatechange = () => {
console.debug("handleMediaStream - status", publishConnection.iceConnectionState);
}

streamDestination.stream.getTracks().forEach(t => {
if (t.kind === 'audio') {
console.debug('handleMediaStream track', t.kind, t.id, t);
peerConnection.addTransceiver(t, {direction: 'sendonly'})
publishConnection.addTransceiver(t, {direction: 'sendonly'})
}
})

const offer = await peerConnection.createOffer();
peerConnection.setLocalDescription(offer);
const offer = await publishConnection.createOffer();
publishConnection.setLocalDescription(offer);
console.debug('handleMediaStream offer', offer.sdp);

window.connection.sendIQ($iq({type: 'set', to: window.connection.domain}).c('whip', {xmlns: 'urn:xmpp:whip:0'}).c('sdp', offer.sdp),
function (res) {
const answer = res.querySelector('sdp').innerHTML;
peerConnection.setRemoteDescription({sdp: answer, type: 'answer'});
publishConnection.setRemoteDescription({sdp: answer, type: 'answer'});
console.debug('handleMediaStream answer', answer);

}, function (err) {
Expand All @@ -978,15 +1047,16 @@ async function handleMediaStream(started) {
);

} else {
if (peerConnection) {
peerConnection.close();
peerConnection = null;
if (publishConnection) {
publishConnection.close();
publishConnection = null;
}
}
}

function startXMPP() {
const streamSong = document.querySelector("#stream_song");
const activeStreams = document.querySelector("#active_streams");

streamSong.addEventListener("click", async (evt) => {
const streamStarted = streamSong.innerText == "Stop Stream";
Expand Down Expand Up @@ -1014,12 +1084,15 @@ function startXMPP() {
if (status === Strophe.Status.CONNECTED) {
window.connection.send($pres());
streamSong.style.display = "";
streamSong.style.setProperty("--accent-fill-rest", "green");
activeStreams.style.display = "";
streamSong.style.setProperty("--accent-fill-rest", "green");
fetchStreams();
}
else

if (status === Strophe.Status.DISCONNECTED) {
streamSong.style.display = "none";
activeStreams.style.display = "none";
}
});
}
Expand Down
4 changes: 2 additions & 2 deletions docs/xep/xep-xxxx-user_media_streams-01-01.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
<section1 topic='Introduction' anchor='intro'>
<p>While WebRTC specifies the usage of SDPs [RFC8866] and an Offer/Answer model [RFC3264] for creating connections, WebRTC does not define the precise signaling protocols over which SDPs should be exchanged [RFC8825]. In order to establish a WebRTC session between two WebRTC entities, a signaling protocol is usually used to exchange SDPs</p>
<p>With HTTP/S applications, WHIP and WHEP have been proposed as simple protocols to ingest and egress media streams using WebRTC.</p>
<p>This specification provideds the means to do likewise in XMPP for communicating user media streams using WebRTC-based ingestion/egress. The WebRTC SDP payload format is transported using set and get IQ stanzas which map directly to WHIP and WHEP.</p>
<p>This specification provideds the means to do likewise in XMPP for communicating user media streams using WebRTC-based ingestion/egress. The WebRTC SDP payload format is transported using IQ stanzas which map directly to WHIP and WHEP REST endpoints.</p>
<p>This specification does not prescribe a method for the discovery, publishing and subscription of available URIs for WHIP and WHEP. It is assumed that this will be achieved with other XMPP mechanisms like Publish-Subscribe (&xep0060;) or HTTP Online Meetings (&xep0483;) for example.</p>
</section1>
<section1 topic="Requirements" anchor="reqs">
Expand Down Expand Up @@ -128,7 +128,7 @@
</whep>
</iq>
]]></example>
<example caption='Client sends an WHEP SDP offer to receive a known user media stream'><![CDATA[
<example caption='Client sends an WHEP SDP offer to receive a specific user media stream'><![CDATA[
<iq from='[email protected]/consumer' id='6338Vbrhrl' type='set'>
<whep id="ih28sx61" xmlns='urn:xmpp:whep:0'>
<sdp>
Expand Down
Loading

0 comments on commit 8ca22d7

Please sign in to comment.