Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ Features:
- sending a welcome message with some statistics to every user that joins our server

## Installation
Tested on Linux, Ubuntu 16.04.5, 18.04.3 and 20.04.2 using Node 11.15 and 14.0
Tested on Linux, Ubuntu 20.10 using Node 16.13

You will need <a href="https://nodejs.org/en/">NodeJS</a>, <a href="https://ffmpeg.org/">ffmpeg</a>, <a href="https://teamspeak.com/en/downloads/">TeamSpeak3 Client</a>, libasound2-dev and desktop environment (to launch the TS3 Client).
You will need <a href="https://nodejs.org/en/">NodeJS</a>, <a href="https://ffmpeg.org/">ffmpeg</a>, <a href="https://teamspeak.com/en/downloads/">TeamSpeak3 Client</a>, python>=2.7, libasound2-dev and desktop environment (to launch the TS3 Client).

### Step 1
- Clone this repo to your local machine
Expand Down
308 changes: 187 additions & 121 deletions package-lock.json

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "ts3-music-bot",
"version": "1.0.0",
"description": "Music Bot for TeamSpeak 3 in NodeJS",
"version": "2.0.0",
"description": "Music Bot for TeamSpeak 3 in NodeJS (using also Python though)",
"main": "src/main.js",
"scripts": {
"start": "node ./src/main.js"
Expand All @@ -26,14 +26,14 @@
"fluent-ffmpeg": "^2.1.2",
"html-entities": "^1.4.0",
"leaguejs": "^2.1.3",
"node-fetch": "latest",
"node-fetch": "^2.6.6",
"node-ts": "^5.1.1",
"prompt-sync": "^4.2.0",
"properties-reader": "^0.3.1",
"replace-in-file": "^4.3.1",
"simple-youtube-api": "latest",
"speaker": "^0.5.3",
"ytdl-core": "latest",
"youtube-dl-exec": "^2.0.2",
"ytpl": "^2.2.1"
}
}
26 changes: 18 additions & 8 deletions src/audio-handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,21 @@ class AudioHandler {
}

/** @param {Function} onEnd */
play(onEnd) {
this.s = stream(this.url, this.client)
.pipe(new speaker({
channels: 2, // 2 channels
bitDepth: 16, // 16-bit samples
sampleRate: 44100,
highWaterMark: 1 << 25
}));
async play(onEnd) {
try {
this.ffmpeg = await stream(this.url, this.client);
} catch (e) {
console.error(e);
onEnd(e);
return;
}

this.s = this.ffmpeg.pipe(new speaker({
channels: 2, // 2 channels
bitDepth: 16, // 16-bit samples
sampleRate: 44100,
highWaterMark: 1 << 25
}));

this.s.on('error', (e) => {
console.error(e);
Expand All @@ -33,15 +40,18 @@ class AudioHandler {
}
sendChannelMessage(this.client, e.message);

this.ffmpeg.kill();
this.s.destroy();
onEnd(e);
});
this.s.on('finish', () => {
this.ffmpeg.kill();
this.s.destroy();
onEnd();
});
}


finish() {
this.s.emit('finish');
}
Expand Down
61 changes: 20 additions & 41 deletions src/audio-stream.js
Original file line number Diff line number Diff line change
@@ -1,55 +1,34 @@
const ytdl = require('ytdl-core');
const fs = require('fs');
const ytdl = require('youtube-dl-exec');
const fetch = require("node-fetch");
const ffmpeg = require('fluent-ffmpeg');
const config = require('./config');
const {sendChannelMessage} = require("./utils");
const {sendChannelMessage, isYouTubeLink} = require("./utils");

if (config.ffmpegExecutablePath)
ffmpeg.setFfmpegPath(config.ffmpegExecutablePath);

let cookies = config.cookiesString;
if (!cookies && Array.isArray(config.cookiesArray) && config.cookiesArray.length > 0) {
try {
cookies = config.cookiesArray;
cookies = cookies.map((cookie) => {
if (!cookie.name || !cookie.value) {
throw "wrong cookiesArray in config, expected array of objects, each containing at least name and value";
}
return `${cookie.name}=${cookie.value}`;
}).join(';');
config.cookiesString = cookies;
fs.writeFile(`${__dirname}/config.json`, JSON.stringify(config, null, 2), (err) => {
if (err) return console.error(err);
});
} catch (e) {
console.error(e);
}
}

function stream(url, client) {
const video = ytdl(url, {
quality: 'highestaudio',
highWaterMark: 1 << 25,
filter: format => format.container === 'webm' && format.audioQuality === "AUDIO_QUALITY_MEDIUM",
requestOptions: {
headers: {
Cookie: cookies
}
}
});
const ytdlOptions = {
dumpSingleJson: true,
noPlaylist: true,
abortOnError: true,
playlistEnd: 1,
youtubeSkipDashManifest: true,
};

return ffmpeg()
.input(video)
async function stream(url, client) {
return ytdl(url, {
...ytdlOptions,
...(config.cookiesEnabled && isYouTubeLink(url)) && {cookie: `${__dirname}/cookies.txt`}
}).then(output => {
if (output?.url) return output.url;
else if (output?.requested_formats) return output?.requested_formats.filter(f => f.format.includes("audio"))[0].url;
else return output.entries.filter(f => f.format.includes("audio"))[0].url;
}).then(url => fetch(url)).then(res => res.body).then(body => ffmpeg().input(body)
.addOption('-f s16le')
.addOption('-acodec pcm_s16le')
.addOption('-ac 2')
.addOption('-ar 44100')
.on('error', (err) => {
if (err.message.includes("410")) {
err.message += "\nMost probably age restricted video, set valid cookie in config file to avoid this error";
sendChannelMessage(client, err.message);
}
});
.on('error', (err) => err));
}

module.exports = stream;
Loading