From 8e50c2f84ea1d0785e7494e46156180bc9d07797 Mon Sep 17 00:00:00 2001 From: Rui Date: Tue, 23 Jan 2024 18:32:40 -0800 Subject: [PATCH] initial commit --- .gitignore | 3 + .prettierrc | 20 ++ README.md | 47 ++++ api.json | 4 + favicon.ico | Bin 0 -> 3456 bytes index.css | 210 ++++++++++++++++ index.html | 68 +++++ index.js | 422 +++++++++++++++++++++++++++++++ package-lock.json | 622 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 15 ++ server.js | 9 + 11 files changed, 1420 insertions(+) create mode 100644 .gitignore create mode 100644 .prettierrc create mode 100644 README.md create mode 100644 api.json create mode 100644 favicon.ico create mode 100644 index.css create mode 100644 index.html create mode 100644 index.js create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 server.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b9cc369 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules +.idea +.vscode \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..a5764d5 --- /dev/null +++ b/.prettierrc @@ -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" } + } + ] +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..2884d07 --- /dev/null +++ b/README.md @@ -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. diff --git a/api.json b/api.json new file mode 100644 index 0000000..283a2b8 --- /dev/null +++ b/api.json @@ -0,0 +1,4 @@ +{ + "apiKey": "YourApiKey", + "serverUrl": "https://api.heygen.com" +} diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..55358da16450caf732518f076675788fcacae3ee GIT binary patch literal 3456 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 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; +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..dc5e8e2 --- /dev/null +++ b/index.html @@ -0,0 +1,68 @@ + + + + + + + + +
+
+
+ + + + + + + +
+ +
+ + + +
+
+ +

+ +
+
+ + +
+ +
+
+ Remove background + +
+ + +
+
+
+ + + + diff --git a/index.js b/index.js new file mode 100644 index 0000000..5bea102 --- /dev/null +++ b/index.js @@ -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 + '
'; + 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(); + } +}); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..dab1637 --- /dev/null +++ b/package-lock.json @@ -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" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..940023a --- /dev/null +++ b/package.json @@ -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" + } +} diff --git a/server.js b/server.js new file mode 100644 index 0000000..b098e23 --- /dev/null +++ b/server.js @@ -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!'); +});