Skip to content

Commit

Permalink
updates live streaming structure
Browse files Browse the repository at this point in the history
  • Loading branch information
TheCodeTherapy committed Nov 7, 2024
1 parent 008b4f9 commit e0f69fd
Show file tree
Hide file tree
Showing 7 changed files with 135 additions and 55 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<m-group id="wrapper">
<m-group id="wrapper" y="4.5" z="-9">
<m-group id="video-wrapper" y="1.5" sx="0.2" sy="0.2" sz="0.2">
<m-image
id="stream-offline-label"
Expand All @@ -12,14 +12,24 @@
</m-group>

<script>
const streamURL = "whep://CLOUDFLARE_STREAM_URL_GOES_HERE";
const streamCheckURL = "//localhost/live-status";

let lastStreamRequestTime = 0;
const minimumRequestIntervalInSeconds = 15;

let streamIsLive = false;
let stream = null;
let streamCheckInProgress = false;

const width = 21;
let videoExpanded = false;

const wrapper = document.getElementById("wrapper");
const videoWrapper = document.getElementById("video-wrapper");
const streamOfflineLabel = document.getElementById("stream-offline-label");
const tv = document.getElementById("tv");

let lastStreamRequestTime = 0;
const minimumRequestIntervalInSeconds = 15;

const playersInProbe = new Set();
const probe = document.createElement("m-position-probe");
probe.setAttribute("debug", false);
Expand All @@ -44,13 +54,6 @@
});
wrapper.appendChild(probe);

const streamURL = "whep://director.millicast.com/api/whep/htmYPz/mml.mgz.me";
let streamIsLive = false;
let stream = null;

const width = 21;
let videoExpanded = false;

function expandVideo() {
if (videoExpanded === true) return;
videoExpanded = true;
Expand All @@ -70,51 +73,55 @@
function watchStream() {
const now = document.timeline.currentTime;
if (now - lastStreamRequestTime < minimumRequestIntervalInSeconds * 1000) return;

if (streamCheckInProgress) return;

streamCheckInProgress = true;
lastStreamRequestTime = now;

var xhr = new XMLHttpRequest();
xhr.open("POST", "https://director.millicast.com/api/director/subscribe", true);
xhr.open("GET", streamCheckURL, true);
xhr.setRequestHeader("Content-Type", "application/json");

xhr.onreadystatechange = function () {
if (this.readyState === XMLHttpRequest.DONE && this.status === 200) {
if (streamIsLive === false) {
streamIsLive = true;
streamOfflineLabel.setAttribute("visible", false);
var jsonData = JSON.parse(xhr.responseText);
// console.log("Call Complete:", jsonData);
stream = document.createElement("m-video");
make16x9(stream, width);
stream.setAttribute("id", "live-stream");
stream.setAttribute("emissive", 1.9);
stream.setAttribute("volume", 3.5);
stream.setAttribute("src", streamURL);
videoWrapper.appendChild(stream);
expandVideo();
}
} else if (this.status !== 200) {
streamIsLive = false;
streamOfflineLabel.setAttribute("visible", true);
if (stream !== null) {
videoWrapper.removeChild(stream);
stream = null;
if (this.readyState === XMLHttpRequest.DONE) {
streamCheckInProgress = false;

if (this.status === 200) {
if (streamIsLive === false) {
streamIsLive = true;
streamOfflineLabel.setAttribute("visible", false);
var jsonData = JSON.parse(xhr.responseText);
stream = document.createElement("m-video");
make16x9(stream, width);
stream.setAttribute("id", "live-stream");
stream.setAttribute("emissive", 1.9);
stream.setAttribute("volume", 3.5);
stream.setAttribute("src", streamURL);
videoWrapper.appendChild(stream);
expandVideo();
}
} else {
console.log("Stream is offline");
streamIsLive = false;
streamOfflineLabel.setAttribute("visible", true);
if (stream !== null) {
videoWrapper.removeChild(stream);
stream = null;
}
shrinkVideo();
}
shrinkVideo();
}
};
xhr.send(
JSON.stringify({
streamAccountId: "htmYPz",
streamName: "mml.mgz.me",
unauthorizedSubscribe: true,
}),
);
xhr.send();
}

setInterval(() => {
const hasWatchers = playersInProbe.size > 0;
if (hasWatchers) {
watchStream();
}
}, 1000);
}, 10000);

function animate(element, attr, start, end, duration, easing, loop = false) {
const anim = document.createElement("m-attr-anim");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@
mFrame.setAttribute("z", z);
mFrame.setAttribute("ry", ry);
} else if (i === 10) {
mFrame.setAttribute("src", "wss:///mml-documents/live-coding.html");
mFrame.setAttribute("src", "wss:///mml-documents/live-stream.html");
mFrame.setAttribute("x", x);
mFrame.setAttribute("y", mmlDocYPos);
mFrame.setAttribute("z", z);
Expand Down
12 changes: 7 additions & 5 deletions example/multi-user-3d-web-experience/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,21 @@
"@mml-io/3d-web-experience-server": "^0.19.0",
"@mml-io/3d-web-text-chat": "^0.19.0",
"@mml-io/3d-web-user-networking": "^0.19.0",
"canvas": "2.11.2",
"chokidar": "^3.6.0",
"cors": "^2.8.5",
"dotenv": "16.4.5",
"express": "4.19.2",
"express-ws": "5.0.2",
"ws": "8.18.0",
"livekit-server-sdk": "2.6.0",
"dotenv": "16.4.5",
"canvas": "2.11.2",
"networked-dom-server": "0.18.0"
"networked-dom-server": "0.18.0",
"ws": "8.18.0"
},
"devDependencies": {
"@types/cors": "2.8.17",
"@types/express": "^4.17.21",
"@types/express-ws": "^3.0.4",
"@types/node": "^20.14.10"
"@types/node": "^20.14.10",
"@types/node-fetch": "^2.6.11"
}
}
46 changes: 46 additions & 0 deletions example/multi-user-3d-web-experience/server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import url from "url";

import { Networked3dWebExperienceServer } from "@mml-io/3d-web-experience-server";
import type { CharacterDescription } from "@mml-io/3d-web-user-networking";
import cors from "cors";
import dotenv from "dotenv";
import express from "express";
import enableWs from "express-ws";
import fetch from "node-fetch";

import { BasicUserAuthenticator } from "./BasicUserAuthenticator";
import { registerDolbyVoiceRoutes, registerLiveKitVoiceRoutes } from "./voice-routes";
Expand Down Expand Up @@ -52,6 +54,7 @@ const mmlDocumentsWatchPath = "**/*.html";
dotenv.config();
const { app } = enableWs(express());
app.enable("trust proxy");
app.use(cors());

const VOICE_CHAT_PASSWORD = process.env.VOICE_CHAT_PASSWORD ?? "";

Expand Down Expand Up @@ -98,6 +101,49 @@ const networked3dWebExperienceServer = new Networked3dWebExperienceServer({
});
networked3dWebExperienceServer.registerExpressRoutes(app);

async function checkLiveStatus(): Promise<boolean> {
const liveCheckURL = `https://api.cloudflare.com/client/v4/accounts/${process.env.CLOUDFLARE_ACCOUNT_ID}/stream/live_inputs/${process.env.CLOUDFLARE_LIVE_INPUT_ID}`;

const liveCheckOptions = {
method: "GET",
headers: {
"Content-Type": "application/json",
"X-Auth-Email": process.env.CLOUDFLARE_AUTH_EMAIL || "",
Authorization: `Bearer ${process.env.CLOUDFLARE_API_TOKEN}`,
},
};

try {
const response = await fetch(liveCheckURL, liveCheckOptions);
const json = await response.json();
if (json.result && json.result.status.current.state === "connected") {
return true;
} else {
return false;
}
} catch (err) {
console.error("error:" + err);
return false;
}
}

if (
process.env.CLOUDFLARE_STREAM_URL &&
process.env.CLOUDFLARE_AUTH_EMAIL &&
process.env.CLOUDFLARE_API_TOKEN &&
process.env.CLOUDFLARE_ACCOUNT_ID &&
process.env.CLOUDFLARE_LIVE_INPUT_ID
) {
app.get("/live-status", async (req, res) => {
const isLive = await checkLiveStatus();
if (isLive) {
res.status(200).json({ live: true });
} else {
res.status(404).json({ live: false });
}
});
}

// Start listening
console.log("Listening on port", PORT);
app.listen(PORT);
14 changes: 13 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions packages/3d-web-experience-server/src/MMLDocumentsServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,25 @@ const checkAPIKey = (mmlDocumentContent: string): string => {
content = content.replace("OPENAI_AGENT_ID_GOES_HERE", process.env.OPENAI_AGENT_ID);
}
}
if (process.env.CLOUDFLARE_STREAM_URL) {
if (content.includes("CLOUDFLARE_STREAM_URL_GOES_HERE")) {
console.log("Cloudflare Stream URL placeholder found in MML document");
content = content.replace(
"CLOUDFLARE_STREAM_URL_GOES_HERE",
process.env.CLOUDFLARE_STREAM_URL,
);
}

if (content.includes("//localhost/live-status")) {
content = content.replace(
"//localhost/live-status",
process.env.NODE_ENV !== "production"
? "http://localhost:8080/live-status"
: "https://mml.mgz.me/live-status",
);
}
//
}
return content;
};

Expand Down

0 comments on commit e0f69fd

Please sign in to comment.