first commit
This commit is contained in:
commit
affba2bc26
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
node_modules
|
62
README.md
Normal file
62
README.md
Normal 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
178
hypertube.js
Normal 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
28
package.json
Normal 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"
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user