From affba2bc2641153687ec49f74e66cc885ebac05a Mon Sep 17 00:00:00 2001 From: raven Date: Mon, 9 Jan 2023 19:08:02 -0500 Subject: [PATCH] first commit --- .gitignore | 1 + README.md | 62 ++++++++++++++++++ hypertube.js | 178 +++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 28 ++++++++ 4 files changed, 269 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 hypertube.js create mode 100644 package.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/README.md b/README.md new file mode 100644 index 0000000..06dfa3e --- /dev/null +++ b/README.md @@ -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. + diff --git a/hypertube.js b/hypertube.js new file mode 100644 index 0000000..12474f6 --- /dev/null +++ b/hypertube.js @@ -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") +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000..2e5e71d --- /dev/null +++ b/package.json @@ -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" + } +}