first commit

This commit is contained in:
raven 2023-01-09 19:08:02 -05:00
commit affba2bc26
4 changed files with 269 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
node_modules

62
README.md Normal file
View File

@ -0,0 +1,62 @@
# HyperTube
A Peer to Peer Audio Streaming Server and client using [Hyper-Protocol](https://docs.holepunch.to/)
A high-level API for finding and connecting to peers who are interested in a "topic."
[@hyperswarm/dht](https://docs.holepunch.to/building-blocks/hyperswarm#dht):
A DHT powering Hyperswarm. Through this DHT, each server is bound to a unique key pair, with the client connecting to the server using the server's public key.
[@hyperswarm/secretstream](https://docs.holepunch.to/building-blocks/hyperswarm#secretstream):
Secretstream is used to securely create connections between two peers in Hyperswarm.
## What is HyperSwarm?
Hyperswarm allows you to find and connect to peers who are announcing a common "topic". The swarm topic can be anything. This means that, with Hyperswarm, you can discover and connect peers with a shared interest over a distributed network. As an example, we often use Hypercore's discovery key as the swarm topic for discovering peers to replicate with.
Once Hyperswarm has discovered peers, nodes in the DHT help those peers connect via a UDP-based hole-punching algorithm that's designed to work well in home networks.
https://docs.holepunch.to/building-blocks/hyperswarm
## Usage
To install:
`git clone git@git.codingvm.codes:snxraven/hypertube.git`
`cd hypertube`
`npm i`
To run a new topic (room):
`node hypertube.js`
This will give you a connection topic to share:
```
/hypertube ✦ node hypertube.js
joined topic: 6076c0903ad293e24c10fceb501fe7b02425f6d26c7a5b2d015abd07e3e6b17b
(Share this key to others so they may join.)
To Play a youtube link, use !play LINKHERE to stop !stop
All commands are global to all peers
```
To connect to an altready made topic (room) pass the hash on start up:
`node hypertube.js 6076c0903ad293e24c10fceb501fe7b02425f6d26c7a5b2d015abd07e3e6b17b`
# Commands
!play [youtube link] - play a youtube link globally to all peers
!stop - Stop all peers audio
Whoever plays the track becomes me broadcaster, all peers will see data coming in in the form of a buffer.
You must !stop the audio before submitting another track.

178
hypertube.js Normal file
View File

@ -0,0 +1,178 @@
const fs = require('fs');
const b4a = require('b4a');
const Hyperswarm = require('hyperswarm');
const gracefulGoodbye = require('graceful-goodbye');
const crypto = require('hypercore-crypto');
let rand = Math.floor(Math.random() * 99999).toString();
let USERNAME = "annon" + rand
const stream = require('youtube-audio-stream')
const decoder = require('@suldashi/lame').Decoder
const Speaker = require('speaker');
let audioPlayer = new Speaker({
channels: 2,
bitDepth: 16,
sampleRate: 44100,
});
// Flag to keep track of whether the stream is currently playing
let isPlaying = false;
function startStream(URL) {
// Create a speaker instance to play the audio data
// Stream the audio file
const audioStream = stream(URL)
.pipe(decoder());
// Send the audio data to all connected peers
audioStream.on('data', data => {
for (const conn of conns) {
conn.write(data);
}
});
// Check if the audio player has ended
if (!audioPlayer.writable) {
// Create a new audio player if it has ended
audioPlayer = new Speaker({
channels: 2,
bitDepth: 16,
sampleRate: 44100,
});
audioStream.pipe(audioPlayer);
isPlaying = true;
} else {
// Pipe the audio data to the audio player
audioStream.pipe(audioPlayer);
// Update the flag to indicate that the stream is playing
isPlaying = true;
}
}
// Kill the stream
function stopStream() {
// Send an empty message to all connected peers
for (const conn of conns) {
conn.write(Buffer.alloc(0));
}
// Update the flag to indicate that the stream is not playing
isPlaying = false;
}
// Generate a random public key
const publicKey = crypto.randomBytes(32);
// Create the swarm and pass in the public key
const swarm = new Hyperswarm();
gracefulGoodbye(() => swarm.destroy());
// Keep track of all connections and usernames
const conns = [];
swarm.on('connection', conn => {
const name = b4a.toString(conn.remotePublicKey, 'hex');
console.log(`* got a connection from ${name} (${USERNAME}) *`);
// Start the stream if it is currently playing
if (isPlaying) {
startStream();
}
conns.push(conn);
conn.once('close', () => conns.splice(conns.indexOf(conn), 1));
conn.on('data', data => {
// Check if the message is empty
if (data.length === 0) {
for (const conn of conns) {
conn.write(`Stopping on all Peers`)
}
// Stop the stream
audioPlayer.end()
// Update the flag to indicate that the stream is not playing
isPlaying = false;
} else {
// Pipe the data to the speaker instance
console.log(data);
try {
if (!audioPlayer.writable) {
console.log("trying")
// Create a new audio player if it has ended
audioPlayer = new Speaker({
channels: 2,
bitDepth: 16,
sampleRate: 44100,
});
audioStream.pipe(audioPlayer);
isPlaying = true;
} else {
audioPlayer.write(data);
}
} catch (err) {
if (err.code === "ERR_STREAM_WRITE_AFTER_END") {
console.log("The stream has already ended, cannot write data.");
} else {
throw err;
}
}
}
});
});
// Use readline to accept input from the user
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
// When the user inputs a line of text, broadcast it to all connections
rl.on('line', input => {
// Check if the user is issuing the !play command
if (input.startsWith('!play')) {
// Check if the stream is currently playing
let dataInfo = input.split(" ")
let URL = dataInfo[1]
startStream(URL);
}
if (input === '!stop') {
// Check if the stream is currently playing
if (isPlaying) {
audioPlayer.end()
// Stop the stream
stopStream();
audioPlayer = new Speaker({
channels: 2,
bitDepth: 16,
sampleRate: 44100,
});
} else {
console.log("The stream is already stopped.");
}
}
});
// Join a common topic
const topic = process.argv[2] ? b4a.from(process.argv[2], 'hex') : crypto.randomBytes(32);
const discovery = swarm.join(topic, { client: true, server: true });
// The flushed promise will resolve when the topic has been fully announced to the DHT
discovery.flushed().then(() => {
console.log(`joined topic: ${b4a.toString(topic, 'hex')}`);
console.log("(Share this key to others so they may join)")
console.log("To Play a youtube link, use !play LINKHERE to stop !stop")
console.log("All commands are global to all peers")
});

28
package.json Normal file
View File

@ -0,0 +1,28 @@
{
"name": "sshchat",
"version": "1.0.0",
"description": "A peer to peer chat client that uses hyper-protocol and connects to the ssh.surf API",
"main": "sshChat.mjs",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git@git.codingvm.codes:snxraven/sshChat-CLI.git"
},
"author": "Raven Scott",
"license": "ISC",
"dependencies": {
"@suldashi/lame": "^1.2.5",
"b4a": "^1.6.1",
"graceful-goodbye": "^1.2.0",
"hypercore-crypto": "^3.3.0",
"hyperswarm": "^4.3.5",
"readline": "^1.3.0",
"sox": "^0.1.0",
"speaker": "^0.5.4",
"unirest": "^0.6.0",
"youtube-audio-stream": "^0.3.61",
"ytdl-core": "^4.11.2"
}
}