initial commit

This commit is contained in:
Rui
2024-01-23 18:32:40 -08:00
commit 8e50c2f84e
11 changed files with 1420 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
node_modules
.idea
.vscode

20
.prettierrc Normal file
View File

@@ -0,0 +1,20 @@
{
"singleQuote": true,
"jsxSingleQuote": false,
"trailingComma": "all",
"printWidth": 100,
"useTabs": false,
"tabWidth": 2,
"semi": true,
"quoteProps": "as-needed",
"bracketSpacing": true,
"bracketSameLine": false,
"arrowParens": "always",
"singleAttributePerLine": false,
"overrides": [
{
"files": ".prettierrc",
"options": { "parser": "json" }
}
]
}

47
README.md Normal file
View File

@@ -0,0 +1,47 @@
# Streaming Avatar Demo
## Pre-Requisites
- **_Apply for becoming a real-time avatar whitelist user_**
- Node.js and npm installed on your system.
- API key for HeyGen's API.
## Run the Demo
1. Clone the repository.
```
git clone https://github.com/HeyGen-Official/StreamingAvatar.git
```
2. Open the `api.json` file and replace `'YourApiKey'` with your API key:
```
"apiKey": "YourApiKey";
```
3. open a terminal in the folder and then install the express and run the server.js:
```
npm install express
node server.js
```
4. you will see `App is listening on port 3000!`.
## Use the Demo
0. Make sure you are whitelisted to use the HeyGen's real-time avatar API.
1. Open the web browser and enter the `http://localhost:3000`to start the demo.
2. Click the "New" button to create a new session. The status updates will be displayed on the screen.
3. After the session is created successfully, click the "Start" button to start streaming the avatar.
4. To send a task to the avatar, type the text in the provided input field and click the "Repeat Text" button.
5. Once done, click the "Close Connection" button to close the session.
Remember, this is a demo and should be modified according to your needs and preferences. Happy coding!
## Troubleshooting
In case you face any issues while running the demo or have any questions, feel free to raise an issue in this repository or contact our support team.
Please note, if you encounter a "Server Error", it could be due to the server being offline. In such cases, please contact the service provider.

4
api.json Normal file
View File

@@ -0,0 +1,4 @@
{
"apiKey": "YourApiKey",
"serverUrl": "https://api.heygen.com"
}

BIN
favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

210
index.css Normal file
View File

@@ -0,0 +1,210 @@
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
background-color: #f5f5f5;
padding: 24px;
}
.main {
display: flex;
flex-direction: column;
gap: 24px;
}
.actionRowsWrap {
display: flex;
flex-direction: column;
gap: 12px;
}
.actionRow {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 12px;
}
.actionRow label {
display: flex;
align-items: center;
gap: 8px;
}
button {
display: inline-block;
padding: 0 16px;
border-radius: 8px;
height: 40px;
background-color: #4caf50; /* Green */
border: none;
color: #fff;
text-align: center;
font-size: 16px;
cursor: pointer;
transition-duration: 0.4s;
}
button:hover {
background-color: #45a049;
}
input {
height: 40px;
padding: 0 12px;
font-size: 16px;
}
#status {
overflow: auto;
background-color: #fff;
height: 120px;
padding: 10px 12px;
border: 1px solid #ccc;
border-radius: 8px;
font-size: 14px;
line-height: 1.6;
}
.videoSectionWrap {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
}
.actionRow.switchRow {
width: 100%;
justify-content: center;
}
.switchRow {
flex-direction: column;
}
.switchRow > label {
width: 100%;
display: flex;
justify-content: center;
}
.switchRow > label input {
flex: 1;
max-width: 500px;
}
.videoSectionWrap .videoWrap {
display: flex;
justify-content: center;
align-items: center;
/*background: linear-gradient(0deg, rgba(0, 0, 0, 0.02) 0%, rgba(0, 0, 0, 0.02) 100%),*/
/* radial-gradient(*/
/* 108.09% 141.42% at 0% 100%,*/
/* rgba(219, 255, 213, 0.3) 0%,*/
/* rgba(255, 255, 255, 0) 100%*/
/* ),*/
/* linear-gradient(135deg, #ffeede 5.71%, #ffd9d9 47%, #a3dce7 93.47%);*/
}
.videoWrap .videoEle {
width: 100%;
max-height: 400px;
}
/*---------- Switch START ----------*/
.switchWrap {
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
}
.switch {
position: relative;
display: inline-block;
width: 60px;
height: 34px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
-webkit-transition: 0.4s;
transition: 0.4s;
}
.slider:before {
position: absolute;
content: '';
height: 26px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: white;
-webkit-transition: 0.4s;
transition: 0.4s;
}
input:checked + .slider {
background-color: #2196f3;
}
input:focus + .slider {
box-shadow: 0 0 1px #2196f3;
}
input:checked + .slider:before {
-webkit-transform: translateX(26px);
-ms-transform: translateX(26px);
transform: translateX(26px);
}
/* Rounded sliders */
.slider.round {
border-radius: 34px;
}
.slider.round:before {
border-radius: 50%;
}
/*---------- Switch END ----------*/
.videoSectionWrap .hide {
display: none;
}
.videoSectionWrap .show {
display: flex;
}
.hide {
display: none;
}
.show {
display: flex;
}

68
index.html Normal file
View File

@@ -0,0 +1,68 @@
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="index.css" />
<link rel="icon" href="./favicon.ico" />
</head>
<body>
<div class="main">
<div class="actionRowsWrap">
<div class="actionRow">
<label>
AvatarName
<input id="avatarName" type="text" />
</label>
<label>
VoiceID
<input id="voiceID" type="text" />
</label>
<button id="newBtn">New</button>
<button id="startBtn">Start</button>
<button id="closeBtn">Close</button>
</div>
<div class="actionRow">
<label>
Message
<input id="taskInput" type="text" />
</label>
<button id="talkBtn">Talk</button>
<button id="repeatBtn">Repeat</button>
</div>
</div>
<p id="status"></p>
<div class="videoSectionWrap">
<div class="videoWrap">
<video id="mediaElement" class="videoEle show" autoplay></video>
<canvas id="canvasElement" class="videoEle hide"></canvas>
</div>
<div class="actionRow switchRow hide" id="bgCheckboxWrap">
<div class="switchWrap">
<span>Remove background</span>
<label class="switch">
<input type="checkbox" id="removeBGCheckbox" />
<span class="slider round"></span>
</label>
</div>
<label>
Background (CSS)
<input
type="text"
id="bgInput"
value='url("https://app.heygen.com/icons/heygen/logo_hori_text_light_bg.svg") center / contain no-repeat'
/>
</label>
</div>
</div>
</div>
<script type="module" src="index.js"></script>
</body>
</html>

422
index.js Normal file
View File

@@ -0,0 +1,422 @@
'use strict';
import heygen_API from './api.json' assert { type: 'json' };
const statusElement = document.querySelector('#status');
const apiKey = heygen_API.apiKey;
const SERVER_URL = heygen_API.serverUrl;
if (apiKey === 'YourApiKey' || SERVER_URL === '') {
alert('Please enter your API key and server URL in the api.json file');
}
let sessionInfo = null;
let peerConnection = null;
function updateStatus(statusElement, message) {
statusElement.innerHTML += message + '<br>';
statusElement.scrollTop = statusElement.scrollHeight;
}
updateStatus(statusElement, 'Please click the new button to create the stream first.');
function onMessage(event) {
const message = event.data;
console.log('Received message:', message);
}
// Create a new WebRTC session when clicking the "New" button
async function createNewSession() {
updateStatus(statusElement, 'Creating new session... please wait');
const avatar = avatarName.value;
const voice = voiceID.value;
// call the new interface to get the server's offer SDP and ICE server to create a new RTCPeerConnection
sessionInfo = await newSession('high', avatar, voice);
const { sdp: serverSdp, ice_servers2: iceServers } = sessionInfo;
// Create a new RTCPeerConnection
peerConnection = new RTCPeerConnection({ iceServers: iceServers });
// When ICE candidate is available, send to the server
peerConnection.onicecandidate = ({ candidate }) => {
console.log('Received ICE candidate:', candidate);
if (candidate) {
handleICE(sessionInfo.session_id, candidate.toJSON());
}
};
// When ICE connection state changes, display the new state
peerConnection.oniceconnectionstatechange = (event) => {
updateStatus(
statusElement,
`ICE connection state changed to: ${peerConnection.iceConnectionState}`,
);
};
// When audio and video streams are received, display them in the video element
peerConnection.ontrack = (event) => {
console.log('Received the track');
if (event.track.kind === 'audio' || event.track.kind === 'video') {
mediaElement.srcObject = event.streams[0];
}
};
// When receiving a message, display it in the status element
peerConnection.ondatachannel = (event) => {
const dataChannel = event.channel;
dataChannel.onmessage = onMessage;
};
// Set server's SDP as remote description
const remoteDescription = new RTCSessionDescription(serverSdp);
await peerConnection.setRemoteDescription(remoteDescription);
updateStatus(statusElement, 'Session creation completed');
updateStatus(statusElement, 'Now.You can click the start button to start the stream');
}
// Start session and display audio and video when clicking the "Start" button
async function startAndDisplaySession() {
if (!sessionInfo) {
updateStatus(statusElement, 'Please create a connection first');
return;
}
updateStatus(statusElement, 'Starting session... please wait');
// Create and set local SDP description
const localDescription = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(localDescription);
// Start session
await startSession(sessionInfo.session_id, localDescription);
updateStatus(statusElement, 'Session started successfully');
}
const taskInput = document.querySelector('#taskInput');
// When clicking the "Send Task" button, get the content from the input field, then send the tas
async function talkHandler() {
if (!sessionInfo) {
updateStatus(statusElement, 'Please create a connection first');
return;
}
updateStatus(statusElement, 'Sending task... please wait');
const text = taskInput.value;
if (text.trim() === '') {
alert('Please enter a task');
return;
}
const resp = await talk(sessionInfo.session_id, text);
updateStatus(statusElement, 'Task sent successfully');
}
// When clicking the "Send Task" button, get the content from the input field, then send the tas
async function repeatHandler() {
if (!sessionInfo) {
updateStatus(statusElement, 'Please create a connection first');
return;
}
updateStatus(statusElement, 'Sending task... please wait');
const text = taskInput.value;
if (text.trim() === '') {
alert('Please enter a task');
return;
}
const resp = await repeat(sessionInfo.session_id, text);
updateStatus(statusElement, 'Task sent successfully');
}
// when clicking the "Close" button, close the connection
async function closeConnectionHandler() {
if (!sessionInfo) {
updateStatus(statusElement, 'Please create a connection first');
return;
}
renderID++;
hideElement(canvasElement);
hideElement(bgCheckboxWrap);
mediaCanPlay = false;
updateStatus(statusElement, 'Closing connection... please wait');
try {
// Close local connection
peerConnection.close();
// Call the close interface
const resp = await stopSession(sessionInfo.session_id);
console.log(resp);
} catch (err) {
console.error('Failed to close the connection:', err);
}
updateStatus(statusElement, 'Connection closed successfully');
}
document.querySelector('#newBtn').addEventListener('click', createNewSession);
document.querySelector('#startBtn').addEventListener('click', startAndDisplaySession);
document.querySelector('#talkBtn').addEventListener('click', talkHandler);
document.querySelector('#repeatBtn').addEventListener('click', repeatHandler);
document.querySelector('#closeBtn').addEventListener('click', closeConnectionHandler);
// new session
async function newSession(quality, avatar_name, voice_id) {
const response = await fetch(`${SERVER_URL}/v1/streaming.new`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': apiKey,
},
body: JSON.stringify({
quality,
avatar_name,
voice: {
voice_id: voice_id,
},
}),
});
if (response.status === 500) {
console.error('Server error');
updateStatus(
statusElement,
'Server Error. Please ask the staff if the service has been turned on',
);
throw new Error('Server error');
} else {
const data = await response.json();
console.log(data.data);
return data.data;
}
}
// start the session
async function startSession(session_id, sdp) {
const response = await fetch(`${SERVER_URL}/v1/streaming.start`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': apiKey,
},
body: JSON.stringify({ session_id, sdp }),
});
if (response.status === 500) {
console.error('Server error');
updateStatus(
statusElement,
'Server Error. Please ask the staff if the service has been turned on',
);
throw new Error('Server error');
} else {
const data = await response.json();
return data.data;
}
}
// submit the ICE candidate
async function handleICE(session_id, candidate) {
const response = await fetch(`${SERVER_URL}/v1/streaming.ice`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': apiKey,
},
body: JSON.stringify({ session_id, candidate }),
});
if (response.status === 500) {
console.error('Server error');
updateStatus(
statusElement,
'Server Error. Please ask the staff if the service has been turned on',
);
throw new Error('Server error');
} else {
const data = await response.json();
return data;
}
}
async function talk(session_id, text) {
const task_type = 'talk';
const response = await fetch(`${SERVER_URL}/v1/streaming.task`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': apiKey,
},
body: JSON.stringify({ session_id, text, task_type }),
});
if (response.status === 500) {
console.error('Server error');
updateStatus(
statusElement,
'Server Error. Please ask the staff if the service has been turned on',
);
throw new Error('Server error');
} else {
const data = await response.json();
return data.data;
}
}
// repeat the text
async function repeat(session_id, text) {
const task_type = 'repeat';
const response = await fetch(`${SERVER_URL}/v1/streaming.task`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': apiKey,
},
body: JSON.stringify({ session_id, text, task_type }),
});
if (response.status === 500) {
console.error('Server error');
updateStatus(
statusElement,
'Server Error. Please ask the staff if the service has been turned on',
);
throw new Error('Server error');
} else {
const data = await response.json();
return data.data;
}
}
// stop session
async function stopSession(session_id) {
const response = await fetch(`${SERVER_URL}/v1/streaming.stop`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': apiKey,
},
body: JSON.stringify({ session_id }),
});
if (response.status === 500) {
console.error('Server error');
updateStatus(statusElement, 'Server Error. Please ask the staff for help');
throw new Error('Server error');
} else {
const data = await response.json();
return data.data;
}
}
const removeBGCheckbox = document.querySelector('#removeBGCheckbox');
removeBGCheckbox.addEventListener('click', () => {
const isChecked = removeBGCheckbox.checked; // status after click
if (isChecked && !sessionInfo) {
updateStatus(statusElement, 'Please create a connection first');
removeBGCheckbox.checked = false;
return;
}
if (isChecked && !mediaCanPlay) {
updateStatus(statusElement, 'Please wait for the video to load');
removeBGCheckbox.checked = false;
return;
}
if (isChecked) {
hideElement(mediaElement);
showElement(canvasElement);
renderCanvas();
} else {
hideElement(canvasElement);
showElement(mediaElement);
renderID++;
}
});
let renderID = 0;
function renderCanvas() {
if (!removeBGCheckbox.checked) return;
hideElement(mediaElement);
showElement(canvasElement);
canvasElement.classList.add('show');
const curRenderID = Math.trunc(Math.random() * 1000000000);
renderID = curRenderID;
const ctx = canvasElement.getContext('2d', { willReadFrequently: true });
if (bgInput.value) {
canvasElement.parentElement.style.background = bgInput.value?.trim();
}
function processFrame() {
if (!removeBGCheckbox.checked) return;
if (curRenderID !== renderID) return;
canvasElement.width = mediaElement.videoWidth;
canvasElement.height = mediaElement.videoHeight;
ctx.drawImage(mediaElement, 0, 0, canvasElement.width, canvasElement.height);
ctx.getContextAttributes().willReadFrequently = true;
const imageData = ctx.getImageData(0, 0, canvasElement.width, canvasElement.height);
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
const red = data[i];
const green = data[i + 1];
const blue = data[i + 2];
// You can implement your own logic here
if (isCloseToGreen([red, green, blue])) {
// if (isCloseToGray([red, green, blue])) {
data[i + 3] = 0;
}
}
ctx.putImageData(imageData, 0, 0);
requestAnimationFrame(processFrame);
}
processFrame();
}
function isCloseToGreen(color) {
const [red, green, blue] = color;
return green > 90 && red < 90 && blue < 90;
}
function hideElement(element) {
element.classList.add('hide');
element.classList.remove('show');
}
function showElement(element) {
element.classList.add('show');
element.classList.remove('hide');
}
const mediaElement = document.querySelector('#mediaElement');
let mediaCanPlay = false;
mediaElement.onloadedmetadata = () => {
mediaCanPlay = true;
mediaElement.play();
showElement(bgCheckboxWrap);
};
const canvasElement = document.querySelector('#canvasElement');
const bgCheckboxWrap = document.querySelector('#bgCheckboxWrap');
const bgInput = document.querySelector('#bgInput');
bgInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
renderCanvas();
}
});

622
package-lock.json generated Normal file
View File

@@ -0,0 +1,622 @@
{
"name": "streamingavatar",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "streamingavatar",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"express": "^4.18.2"
},
"devDependencies": {
"prettier": "^3.2.4"
}
},
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
"dependencies": {
"mime-types": "~2.1.34",
"negotiator": "0.6.3"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
},
"node_modules/body-parser": {
"version": "1.20.1",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
"integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
"dependencies": {
"bytes": "3.1.2",
"content-type": "~1.0.4",
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "1.2.0",
"http-errors": "2.0.0",
"iconv-lite": "0.4.24",
"on-finished": "2.4.1",
"qs": "6.11.0",
"raw-body": "2.5.1",
"type-is": "~1.6.18",
"unpipe": "1.0.0"
},
"engines": {
"node": ">= 0.8",
"npm": "1.2.8000 || >= 1.4.16"
}
},
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/call-bind": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
"dependencies": {
"function-bind": "^1.1.1",
"get-intrinsic": "^1.0.2"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/content-disposition": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
"dependencies": {
"safe-buffer": "5.2.1"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/content-type": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
},
"node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/destroy": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
"engines": {
"node": ">= 0.8",
"npm": "1.2.8000 || >= 1.4.16"
}
},
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
},
"node_modules/encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
},
"node_modules/etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/express": {
"version": "4.18.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
"integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
"body-parser": "1.20.1",
"content-disposition": "0.5.4",
"content-type": "~1.0.4",
"cookie": "0.5.0",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"finalhandler": "1.2.0",
"fresh": "0.5.2",
"http-errors": "2.0.0",
"merge-descriptors": "1.0.1",
"methods": "~1.1.2",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
"path-to-regexp": "0.1.7",
"proxy-addr": "~2.0.7",
"qs": "6.11.0",
"range-parser": "~1.2.1",
"safe-buffer": "5.2.1",
"send": "0.18.0",
"serve-static": "1.15.0",
"setprototypeof": "1.2.0",
"statuses": "2.0.1",
"type-is": "~1.6.18",
"utils-merge": "1.0.1",
"vary": "~1.1.2"
},
"engines": {
"node": ">= 0.10.0"
}
},
"node_modules/finalhandler": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
"integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
"dependencies": {
"debug": "2.6.9",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
"statuses": "2.0.1",
"unpipe": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
"node_modules/get-intrinsic": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz",
"integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==",
"dependencies": {
"function-bind": "^1.1.1",
"has": "^1.0.3",
"has-proto": "^1.0.1",
"has-symbols": "^1.0.3"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"dependencies": {
"function-bind": "^1.1.1"
},
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/has-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
"integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-symbols": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/http-errors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
"dependencies": {
"depd": "2.0.0",
"inherits": "2.0.4",
"setprototypeof": "1.2.0",
"statuses": "2.0.1",
"toidentifier": "1.0.1"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"node_modules/ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
"engines": {
"node": ">= 0.10"
}
},
"node_modules/media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/merge-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
"integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
},
"node_modules/methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
"bin": {
"mime": "cli.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/object-inspect": {
"version": "1.12.3",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
"integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/on-finished": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
"dependencies": {
"ee-first": "1.1.1"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/path-to-regexp": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
},
"node_modules/prettier": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.4.tgz",
"integrity": "sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==",
"dev": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
"dependencies": {
"forwarded": "0.2.0",
"ipaddr.js": "1.9.1"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/qs": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
"dependencies": {
"side-channel": "^1.0.4"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/raw-body": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
"integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
"dependencies": {
"bytes": "3.1.2",
"http-errors": "2.0.0",
"iconv-lite": "0.4.24",
"unpipe": "1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"node_modules/send": {
"version": "0.18.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
"integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
"dependencies": {
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "1.2.0",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"fresh": "0.5.2",
"http-errors": "2.0.0",
"mime": "1.6.0",
"ms": "2.1.3",
"on-finished": "2.4.1",
"range-parser": "~1.2.1",
"statuses": "2.0.1"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/send/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"node_modules/serve-static": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
"integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
"dependencies": {
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"parseurl": "~1.3.3",
"send": "0.18.0"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
},
"node_modules/side-channel": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
"dependencies": {
"call-bind": "^1.0.0",
"get-intrinsic": "^1.0.2",
"object-inspect": "^1.9.0"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
"engines": {
"node": ">=0.6"
}
},
"node_modules/type-is": {
"version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
"dependencies": {
"media-typer": "0.3.0",
"mime-types": "~2.1.24"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
"engines": {
"node": ">= 0.8"
}
}
}
}

15
package.json Normal file
View File

@@ -0,0 +1,15 @@
{
"name": "streamingavatar",
"version": "1.0.0",
"description": "## Pre-Requisites",
"main": "index.js",
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.18.2"
},
"devDependencies": {
"prettier": "^3.2.4"
}
}

9
server.js Normal file
View File

@@ -0,0 +1,9 @@
const express = require('express');
const path = require('path');
const app = express();
app.use(express.static(path.join(__dirname, '.')));
app.listen(3000, function () {
console.log('App is listening on port 3000!');
});