diff --git a/Source/HTTPServer.js b/Source/HTTPServer.js index b549be8..ab99942 100644 --- a/Source/HTTPServer.js +++ b/Source/HTTPServer.js @@ -30,12 +30,11 @@ app.get("/test",(request, response)=>{ app.get("/index.js.map",(request, response)=>{ response.sendFile(resolve("./script/index.js.map")) }); -app.get("/webrtc.js",(request, response)=>{ - response.sendFile(resolve("./script/webrtc.js")) -}); -app.get("/webrtc.adapter.js",(request, response)=>{ - response.sendFile(resolve("./script/webrtc.adapter.js")) +app.get("/stream",(request, response)=>{ + response.sendFile(resolve("./public/index.html")) }); +app.use("/stream",express.static(resolve("./public"))); + app.get("/",(request, response)=>{ response.sendFile(resolve("./script/index.html")) }); diff --git a/public/index.css b/public/index.css new file mode 100644 index 0000000..11f8050 --- /dev/null +++ b/public/index.css @@ -0,0 +1,93 @@ +html,body{ + margin: 0; + height: 100%; +} +body{ + background-color: #141414; +} +.root{ + gap: 10px; + padding: 10px; + height: 100%; + display: flex; + flex-wrap: nowrap; + flex-direction: row; + box-sizing: border-box; +} +.dialoque{ + flex: 1; + display: flex; + flex-wrap: nowrap; + flex-direction: row; + position: relative; +} +.videolist{ + flex: 0 0 200px; + display: flex; + flex-wrap: nowrap; + flex-direction: column; + gap: 10px; + overflow: auto; + padding-right: 10px; + cursor: pointer; +} +.dialoque #primaryVideo{ + width: 100%; + height: 100%; + box-sizing: border-box; + object-fit: contain; + background-color: transparent/*#282828*/; + border-radius: 10px; + z-index: 2; +} +.dialoque #primaryVideoShadow{ + width: 100%; + height: 100%; + object-fit: cover; + border-radius: 10px; + position: absolute; + z-index: 1; + left: 0; + top: 0; +} +.dialoque #secondaryVideo{ + width: 300px; + height: 200px; + min-width: 300px; + max-width: 30%; + min-height: 200px; + max-height: 35%; + position: absolute; + right: 10px; + bottom: 10px; + background-color: #383838; + border-radius: 10px; + z-index: 3; + outline-offset: 0px; + outline-style: solid; + outline-width: 5px; + outline-color: rgba(0,0,0,.5); +} +.videolist .frame{ + max-width: 200px; + object-fit: cover; + border-radius: 10px; + border: solid 3px transparent; +} +.videolist .frame.active{ + border: solid 3px green; + box-shadow: 0px 0px 20px -10px green; +} +video{ + image-rendering: pixelated; +} +::-webkit-scrollbar { + width: 6px; +} +::-webkit-scrollbar-thumb { + background: white; + border-radius: 10px; +} +::-webkit-scrollbar-track { + background-color: #ffffff1f; +} \ No newline at end of file diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..8945683 --- /dev/null +++ b/public/index.html @@ -0,0 +1,42 @@ + + + + + + saQüt Video Streaming + + + + +
+
+ + + +
+
+ +
+
+ + + + \ No newline at end of file diff --git a/public/index.js b/public/index.js new file mode 100644 index 0000000..c7193a4 --- /dev/null +++ b/public/index.js @@ -0,0 +1,259 @@ +/** + * @type {import("./MWSE/index").default} + */ +let mwse; +/** + * @type {string} + */ +let mySocketId; +/** + * @type {string} + */ +let myIPAddress; +/** + * @type {string} + */ +let myNumber; +/** + * @type {string} + */ +let roomid; +/** + * @type {import("./MWSE/Room").default} + */ +let room; +/** + * @type {MediaStream} + */ +let primaryVideoContent; +/** + * @type {HTMLVideoElement} + */ +let primaryVideo; +/** + * @type {HTMLVideoElement} + */ +let primaryVideoShadow; +/** + * @type {HTMLVideoElement} + */ +let secondaryVideo; +/** + * @type {MediaStream} + */ +let outgoingStream; +/** + * @type {MediaStream} + */ +let outgoingStreamOnlyVideo; +/** + * @type {HTMLDivElement} + */ +let videoContainer = document.querySelector(".videolist"); + +function connect() +{ + mwse = new MWSE({ + endpoint: "wss://ws.saqut.com" + }); + + mwse.scope(beginEngine); +} +/** + * @type {HTMLVideoElement} + */ +let activeVideo; + +function setPrimaryVideo(video, soundOn) +{ + primaryVideo.srcObject = video; + primaryVideoShadow.srcObject = video; + if(soundOn == undefined) + { + primaryVideo.muted = 1; + primaryVideo.volume = 0; + }else if(soundOn){ + primaryVideo.muted = 0; + primaryVideo.volume = 1; + }else{ + primaryVideo.muted = 0; + primaryVideo.volume = 0; + } +} +function setSecondaryVideo(video) +{ + secondaryVideo.srcObject = video; +} +function templateVideo(name, stream) +{ + let i = document.createElement("video"); + i.muted = 1; + i.classList.add("frame") + i.playsInline = 1; + i.autoplay = 1; + i.dataset.name = name; + if(stream) i.srcObject = stream; + return i; +} +function addVideoList(name, stream, peer) +{ + if(!videoContainer.querySelector(`[name="${name}"]`)) + { + let video = templateVideo(name, stream); + video.dataset.user = peer.socketId; + video.onclick = function(){ + if(activeVideo) + { + activeVideo.classList.remove("active"); + }; + video.classList.add("active"); + activeVideo = video; + setPrimaryVideo(stream, true); + }; + videoContainer.appendChild(video); + } +} + +function removeVideoList(name) +{ + if(videoContainer.querySelector(`[data-name="${name}"]`)) + { + let k = videoContainer.querySelector(`[data-name="${name}"]`); + let user = k.dataset.user; + if(k.dataset.user == activeVideo.dataset.user) + { + setPrimaryVideo(outgoingStreamOnlyVideo, false); + } + k.remove(); + } +} + +async function beginEngine() +{ + let me = mwse.peer("me"); + me.disablePairAuth(); + mySocketId = me.socketId; + myIPAddress = await mwse.virtualPressure.allocAPIPAddress(); + myNumber = await mwse.virtualPressure.allocAPNumber(); + + let url = new URL(window.location); + roomid = url.searchParams.get("room"); + if(!!roomid == 0) + { + let hash = window.crypto.randomUUID(); + url.searchParams.set("room", hash); + window.location = url.href; + }; + + connectRoom(roomid); +}; + +window.addEventListener("load", () => { + primaryVideo = document.querySelector("#primaryVideo"); + secondaryVideo = document.querySelector("#secondaryVideo"); + primaryVideoShadow = document.querySelector("#primaryVideoShadow"); + connect() +}); + +async function startOutgoingWebcam() +{ + outgoingStream = await navigator.mediaDevices.getUserMedia({video: true, audio: true}); + outgoingStreamOnlyVideo = new MediaStream(outgoingStream); + outgoingStreamOnlyVideo.removeTrack(outgoingStreamOnlyVideo.getAudioTracks()[0]) +} + +async function connectRoom() +{ + await startOutgoingWebcam(); + + room = mwse.room({ + name: roomid, + joinType: "free", + accessType: "private", + description: "Private free joined room", + ifexistsJoin: true, + notifyActionEjected: true, + notifyActionInvite: false, + notifyActionJoined: true + }); + await room.createRoom(); + + room.on("join", peer => IncomingPeer(peer, true)); + room.on("eject", peer => OutgoingPeer(peer)); + + for (const peer of await room.fetchPeers()) { + IncomingPeer(peer) + } + if(!primaryVideoContent) + { + setPrimaryVideo(outgoingStreamOnlyVideo); + } + setSecondaryVideo(outgoingStreamOnlyVideo); + + addVideoList("My Webcam",outgoingStreamOnlyVideo, mwse.peer("me")) + +} +/** + * @param {import("./MWSE/Peer").default} peer + */ +function IncomingPeer(peer,activeConnect) +{ + let sendedOTP = false; + peer.createRTC({ + iceServers:[{ + urls: "turn:161.97.136.175:3478", + username: "argist-eu-east-25", + credential: "ee7df17eed35f4cf5a207777f3c0cd7d3b1901a5de7aff52ea55742289d7fee2" + },{ + urls: "stun:stun.l.google.com:19302" + },{ + urls: "stun:stun1.l.google.com:19302" + },{ + urls: "stun:stun2.l.google.com:19302" + },{ + urls: "stun:stun3.l.google.com:19302" + },{ + urls: "stun:stun4.l.google.com:19302" + }], + bundlePolicy: "max-compat", + iceCandidatePoolSize: 0, + iceTransportPolicy: "all" + }); + if(activeConnect) + { + peer.rtc.connect(); + } + peer.rtc.on('connected',() => { + if(!activeConnect && !sendedOTP) + { + sendedOTP = true; + peer.rtc.sendStream(outgoingStream, "Webcam", {}); + } + }); + peer.rtc.on('disconnected',() => { + removeVideoList(peer.streamY, peer); + }); + peer.rtc.on("stream:added", ({stream,name}) => { + peer.streamY = peer.socketId + " | " + name + " - " + stream.id; + addVideoList(peer.socketId + " | " + name + " - " + stream.id,stream, peer); + + if(!primaryVideoContent) + { + primaryVideoContent = stream; + setPrimaryVideo(primaryVideoContent, true); + } + + if(activeConnect && !sendedOTP) + { + sendedOTP = true; + peer.rtc.sendStream(outgoingStream, "Webcam", {}); + } + }) +} +/** + * @param {import("./MWSE/Peer").default} peer + */ +function OutgoingPeer(peer) +{ + removeVideoList(peer.streamY, peer); +} \ No newline at end of file