179 lines
4.6 KiB
JavaScript
179 lines
4.6 KiB
JavaScript
|
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")
|
||
|
});
|