From f12115159e3e888579c9fb8850dc299d515e4af6 Mon Sep 17 00:00:00 2001 From: Speng Reb Date: Tue, 22 Jul 2025 01:02:34 +0200 Subject: [PATCH 1/2] Accepts whep from streem.vereto.net --- bin/build-player.js | 1 + player/update.coffee | 1 + player/whepplayer.coffee | 46 ++++++++++++++++++++++++++++++++++++++++ src/get-info.js | 7 ++++++ www/js/util.js | 4 +++- 5 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 player/whepplayer.coffee diff --git a/bin/build-player.js b/bin/build-player.js index 63e2bebc..594405ac 100755 --- a/bin/build-player.js +++ b/bin/build-player.js @@ -19,6 +19,7 @@ var order = [ 'playerjs.coffee', 'iframechild.coffee', 'odysee.coffee', + 'whepplayer.coffee', 'streamable.coffee', // iframe embed-based players diff --git a/player/update.coffee b/player/update.coffee index a25b6ad7..0a39aca0 100644 --- a/player/update.coffee +++ b/player/update.coffee @@ -18,6 +18,7 @@ TYPE_MAP = bc: IframeChild bn: IframeChild od: OdyseePlayer + wp: WhepPlayer nv: NicoPlayer window.loadMediaPlayer = (data) -> diff --git a/player/whepplayer.coffee b/player/whepplayer.coffee new file mode 100644 index 00000000..33df29f8 --- /dev/null +++ b/player/whepplayer.coffee @@ -0,0 +1,46 @@ +window.WhepPlayer = class WhepPlayer extends PlayerJSPlayer + constructor: (data) -> + super(data) + @load(data) + + load: (data) -> + @ready = false + @setMediaProperties(data) + + { whepURL, streamKey } = data.meta + + waitUntilDefined window, 'PlayerJSPlayer', => + videoEl = document.createElement 'video' + videoEl.autoplay = true + videoEl.muted = true + videoEl.controls = true + removeOld(videoEl) + @setupPlayer(videoEl, data) + + pc = new RTCPeerConnection() + pc.addTransceiver 'audio', direction: 'recvonly' + pc.addTransceiver 'video', direction: 'recvonly' + + pc.ontrack = (event) -> + videoEl.srcObject = event.streams[0] + + pc.oniceconnectionstatechange = -> + document.getElementById('connectionState').innerText = pc.iceConnectionState + + pc.createOffer().then (offer) -> + pc.setLocalDescription offer + fetch whepURL, + method: 'POST' + headers: + 'Authorization': "Bearer #{streamKey}" + 'Content-Type': 'application/sdp' + body: offer.sdp + .then (r) -> r.text() + .then (answer) -> + pc.setRemoteDescription type: 'answer', sdp: answer + + pc.onconnectionstatechange = -> + if pc.connectionState is 'connected' + @ready = true + @fire 'ready' + document.getElementById('readyFlag').innerText = 'yes' diff --git a/src/get-info.js b/src/get-info.js index ad741281..0dd0d8e4 100644 --- a/src/get-info.js +++ b/src/get-info.js @@ -281,6 +281,13 @@ var Getters = { }); }, + wp: function (id, callback) { + const streemId = new URL(id).pathname.split('/').filter(Boolean)[0]; + var title = `${streemId}'s WHEP Livestream`; + var media = new Media(id, title, "--:--", "wp"); + callback(false, media); + }, + /* rtmp stream */ rt: function (id, callback) { var title = "Livestream"; diff --git a/www/js/util.js b/www/js/util.js index 674e358a..f2ab21a7 100644 --- a/www/js/util.js +++ b/www/js/util.js @@ -1347,7 +1347,7 @@ function parseMediaLink(url) { if(data.protocol == 'rtmp:') { return { type: 'rt', id: url }; - } + } if (data.pathname.match(/\.m3u8$/)) { return { type: 'hl', id: url }; } @@ -1356,6 +1356,8 @@ function parseMediaLink(url) { } switch(data.hostname.replace('www.', '')){ + case 'streem.vereto.net': + return { type: 'wp', id: url } case 'youtube.com': if(data.pathname == '/watch'){ return { type: 'yt', id: data.searchParams.get('v') } From 2aabaf6b6dff3442921488824b39b175d90c7cfc Mon Sep 17 00:00:00 2001 From: Speng Reb Date: Tue, 22 Jul 2025 18:33:14 +0200 Subject: [PATCH 2/2] Update whepplayer to be a generic player instead of messing with playerJS --- player/whepplayer.coffee | 93 +++++++++++++++++++++++++++++----------- 1 file changed, 67 insertions(+), 26 deletions(-) diff --git a/player/whepplayer.coffee b/player/whepplayer.coffee index 33df29f8..7cd7f2b4 100644 --- a/player/whepplayer.coffee +++ b/player/whepplayer.coffee @@ -1,46 +1,87 @@ -window.WhepPlayer = class WhepPlayer extends PlayerJSPlayer +window.WhepPlayer = class WhepPlayer extends Player constructor: (data) -> + return new WhepPlayer(data) unless this instanceof WhepPlayer super(data) @load(data) load: (data) -> - @ready = false + @ready = false + @paused = true @setMediaProperties(data) + unless data.meta?.whepURL + u = new URL(data.id) + streemId = u.pathname.split('/').filter(Boolean)[0] + data.meta ?= {} + # Adjust these to whatever your backend expects + data.meta.whepURL = "#{u.origin}/api/whep" + data.meta.streamKey = streemId + { whepURL, streamKey } = data.meta - waitUntilDefined window, 'PlayerJSPlayer', => - videoEl = document.createElement 'video' - videoEl.autoplay = true - videoEl.muted = true - videoEl.controls = true - removeOld(videoEl) - @setupPlayer(videoEl, data) + # Create video element + videoEl = document.createElement 'video' + videoEl.autoplay = true + videoEl.muted = true + videoEl.controls = true - pc = new RTCPeerConnection() - pc.addTransceiver 'audio', direction: 'recvonly' - pc.addTransceiver 'video', direction: 'recvonly' + # Use the standard container swapper + holder = removeOld() # jQuery object + holder.empty().append(videoEl) - pc.ontrack = (event) -> - videoEl.srcObject = event.streams[0] + @videoEl = videoEl - pc.oniceconnectionstatechange = -> - document.getElementById('connectionState').innerText = pc.iceConnectionState + # ---- WebRTC setup ---- + pc = new RTCPeerConnection() + pc.addTransceiver 'audio', direction: 'recvonly' + pc.addTransceiver 'video', direction: 'recvonly' - pc.createOffer().then (offer) -> + pc.ontrack = (e) => @videoEl.srcObject = e.streams[0] + + pc.onconnectionstatechange = => + if pc.connectionState is 'connected' + @ready = true + @fire? 'ready' + + pc.createOffer() + .then (offer) => pc.setLocalDescription offer fetch whepURL, method: 'POST' headers: - 'Authorization': "Bearer #{streamKey}" + Authorization: "Bearer #{streamKey}" 'Content-Type': 'application/sdp' body: offer.sdp - .then (r) -> r.text() - .then (answer) -> - pc.setRemoteDescription type: 'answer', sdp: answer + .then (r) -> r.text() + .then (answer) -> + pc.setRemoteDescription type: 'answer', sdp: answer + .catch (err) -> + console.error 'WHEP negotiation failed:', err - pc.onconnectionstatechange = -> - if pc.connectionState is 'connected' - @ready = true - @fire 'ready' - document.getElementById('readyFlag').innerText = 'yes' + @pc = pc + + play: -> + @paused = false + @videoEl?.play?() + + pause: -> + @paused = true + @videoEl?.pause?() + + seekTo: (t) -> + @videoEl?.currentTime = t if @ready + + setVolume: (v) -> + @videoEl?.volume = v if @ready + + getTime: (cb) -> + if @ready and @videoEl? + cb @videoEl.currentTime + else + cb 0 + + getVolume: (cb) -> + if @ready and @videoEl? + cb @videoEl.volume + else + cb VOLUME