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