start work with broadcast streams

This commit is contained in:
Amish Shah
2016-12-29 21:22:13 +00:00
parent e9af3f0a1f
commit 72a99f9582
6 changed files with 153 additions and 10 deletions

View File

@@ -11,6 +11,7 @@ const ActionsManager = require('./actions/ActionsManager');
const Collection = require('../util/Collection'); const Collection = require('../util/Collection');
const Presence = require('../structures/Presence').Presence; const Presence = require('../structures/Presence').Presence;
const ShardClientUtil = require('../sharding/ShardClientUtil'); const ShardClientUtil = require('../sharding/ShardClientUtil');
const VoiceBroadcast = require('./voice/VoiceBroadcast');
/** /**
* The starting point for making a Discord Bot. * The starting point for making a Discord Bot.
@@ -136,6 +137,12 @@ class Client extends EventEmitter {
*/ */
this.readyAt = null; this.readyAt = null;
/**
* An array of voice broadcasts
* @type {VoiceBroadcast[]}
*/
this.broadcasts = [];
/** /**
* The previous heartbeat pings of the websocket (most recent first, limited to three elements) * The previous heartbeat pings of the websocket (most recent first, limited to three elements)
* @type {number[]} * @type {number[]}
@@ -219,6 +226,12 @@ class Client extends EventEmitter {
return typeof window !== 'undefined'; return typeof window !== 'undefined';
} }
createVoiceBroadcast() {
const broadcast = new VoiceBroadcast(this);
this.broadcasts.push(broadcast);
return broadcast;
}
/** /**
* Logs the client in. If successful, resolves with the account's token. <warn>If you're making a bot, it's * Logs the client in. If successful, resolves with the account's token. <warn>If you're making a bot, it's
* much better to use a bot account rather than a user account. * much better to use a bot account rather than a user account.

View File

@@ -0,0 +1,85 @@
const EventEmitter = require('events').EventEmitter;
const Prism = require('prism-media');
const ffmpegArguments = [
'-analyzeduration', '0',
'-loglevel', '0',
'-f', 's16le',
'-ar', '48000',
'-ac', '2',
];
class VoiceBroadcast extends EventEmitter {
constructor(client) {
super();
this.client = client;
this.dispatchers = [];
this.prism = new Prism();
this.currentTranscoder = null;
}
get _playableStream() {
if (!this.currentTranscoder) return null;
return this.currentTranscoder.transcoder.output || this.currentTranscoder.options.stream;
}
registerDispatcher(dispatcher) {
if (!this.dispatchers.includes(dispatcher)) this.dispatchers.push(dispatcher);
}
killCurrentTranscoder() {
if (this.currentTranscoder) {
if (this.currentTranscoder.transcoder) this.currentTranscoder.transcoder.kill();
this.currentTranscoder = null;
}
}
playStream(stream, { seek = 0, volume = 1, passes = 1 } = {}) {
const options = { seek, volume, passes };
options.stream = stream;
return this._playTranscodable(stream, options);
}
playFile(file, { seek = 0, volume = 1, passes = 1 } = {}) {
const options = { seek, volume, passes };
return this._playTranscodable(file, options);
}
_playTranscodable(media, options) {
this.killCurrentTranscoder();
const transcoder = this.prism.transcode({
type: 'ffmpeg',
media,
ffmpegArguments: ffmpegArguments.concat(['-ss', String(options.seek)]),
});
transcoder.once('error', e => this.emit('error', e));
transcoder.once('end', () => this.killCurrentTranscoder());
this.currentTranscoder = {
transcoder,
options,
};
transcoder.output.once('readable', () => this._startPlaying());
return this;
}
playConvertedStream(stream, { seek = 0, volume = 1, passes = 1 } = {}) {
this.killCurrentTranscoder();
const options = { seek, volume, passes, stream };
this.currentTranscoder = { options };
stream.once('readable', () => this._startPlaying());
return this;
}
_startPlaying() {
if (!this._playableStream) return;
const stream = this._playableStream;
const buffer = stream.read(1920 * 2);
for (const dispatcher of this.dispatchers) {
setImmediate(() => dispatcher.process(buffer, true));
}
setTimeout(this._startPlaying.bind(this), 20);
}
}
module.exports = VoiceBroadcast;

View File

@@ -265,7 +265,11 @@ class VoiceConnection extends EventEmitter {
*/ */
playConvertedStream(stream, { seek = 0, volume = 1, passes = 1 } = {}) { playConvertedStream(stream, { seek = 0, volume = 1, passes = 1 } = {}) {
const options = { seek, volume, passes }; const options = { seek, volume, passes };
return this.player.playPCMStream(stream, null, options); return this.player.playPCMStream(stream, options);
}
playBroadcast(broadcast) {
return this.player.playBroadcast(broadcast);
} }
/** /**

View File

@@ -1,5 +1,6 @@
const EventEmitter = require('events').EventEmitter; const EventEmitter = require('events').EventEmitter;
const NaCl = require('tweetnacl'); const NaCl = require('tweetnacl');
const VoiceBroadcast = require('../VoiceBroadcast');
const nonce = new Buffer(24); const nonce = new Buffer(24);
nonce.fill(0); nonce.fill(0);
@@ -20,10 +21,14 @@ class StreamDispatcher extends EventEmitter {
super(); super();
this.player = player; this.player = player;
this.stream = stream; this.stream = stream;
this.startStreaming(); if (!(this.stream instanceof VoiceBroadcast)) this.startStreaming();
this.streamOptions = streamOptions; this.streamOptions = streamOptions;
this.streamOptions.volume = this.streamOptions.volume || 0; this.streamOptions.volume = this.streamOptions.volume || 0;
const data = this.streamingData;
data.length = 20;
data.missed = 0;
/** /**
* Whether playing is paused * Whether playing is paused
* @type {boolean} * @type {boolean}
@@ -163,7 +168,7 @@ class StreamDispatcher extends EventEmitter {
return out; return out;
} }
process() { process(buffer, controlled) {
try { try {
if (this.destroyed) { if (this.destroyed) {
this.setSpeaking(false); this.setSpeaking(false);
@@ -180,7 +185,14 @@ class StreamDispatcher extends EventEmitter {
if (this.paused) { if (this.paused) {
// data.timestamp = data.timestamp + 4294967295 ? data.timestamp + 960 : 0; // data.timestamp = data.timestamp + 4294967295 ? data.timestamp + 960 : 0;
data.pausedTime += data.length * 10; data.pausedTime += data.length * 10;
this.player.voiceConnection.voiceManager.client.setTimeout(() => this.process(), data.length * 10); // if buffer is provided we are assuming a master process is controlling the dispatcher
if (!buffer) this.player.voiceConnection.voiceManager.client.setTimeout(() => this.process(), data.length * 10);
return;
}
if (!buffer && controlled) {
data.missed++;
data.pausedTime += data.length * 10;
return; return;
} }
@@ -196,12 +208,14 @@ class StreamDispatcher extends EventEmitter {
} }
const bufferLength = 1920 * data.channels; const bufferLength = 1920 * data.channels;
let buffer = this.stream.read(bufferLength); if (!controlled) {
if (!buffer) { buffer = this.stream.read(bufferLength);
data.missed++; if (!buffer) {
data.pausedTime += data.length * 10; data.missed++;
this.player.voiceConnection.voiceManager.client.setTimeout(() => this.process(), data.length * 10); data.pausedTime += data.length * 10;
return; this.player.voiceConnection.voiceManager.client.setTimeout(() => this.process(), data.length * 10);
return;
}
} }
data.missed = 0; data.missed = 0;
@@ -219,6 +233,7 @@ class StreamDispatcher extends EventEmitter {
data.timestamp = data.timestamp + 4294967295 ? data.timestamp + 960 : 0; data.timestamp = data.timestamp + 4294967295 ? data.timestamp + 960 : 0;
this.sendBuffer(buffer, data.sequence, data.timestamp); this.sendBuffer(buffer, data.sequence, data.timestamp);
if (controlled) return;
const nextTime = data.length + (data.startTime + data.pausedTime + (data.count * data.length) - Date.now()); const nextTime = data.length + (data.startTime + data.pausedTime + (data.count * data.length) - Date.now());
this.player.voiceConnection.voiceManager.client.setTimeout(() => this.process(), nextTime); this.player.voiceConnection.voiceManager.client.setTimeout(() => this.process(), nextTime);
} catch (e) { } catch (e) {

View File

@@ -3,6 +3,7 @@ const Prism = require('prism-media');
const StreamDispatcher = require('../dispatcher/StreamDispatcher'); const StreamDispatcher = require('../dispatcher/StreamDispatcher');
const Collection = require('../../../util/Collection'); const Collection = require('../../../util/Collection');
const OpusEncoders = require('../opus/OpusEngineList'); const OpusEncoders = require('../opus/OpusEngineList');
const VoiceBroadcast = require('../VoiceBroadcast');
const ffmpegArguments = [ const ffmpegArguments = [
'-analyzeduration', '0', '-analyzeduration', '0',
@@ -33,6 +34,10 @@ class AudioPlayer extends EventEmitter {
} }
destroyStream(stream) { destroyStream(stream) {
if (stream instanceof VoiceBroadcast) {
this.streams.delete(stream);
return;
}
const data = this.streams.get(stream); const data = this.streams.get(stream);
if (!data) return; if (!data) return;
const transcoder = data.transcoder; const transcoder = data.transcoder;
@@ -77,6 +82,18 @@ class AudioPlayer extends EventEmitter {
dispatcher.on('error', () => this.destroyStream(stream)); dispatcher.on('error', () => this.destroyStream(stream));
return dispatcher; return dispatcher;
} }
playBroadcast(broadcast, { volume = 1, passes = 1 } = {}) {
const options = { volume, passes };
this.destroyAllStreams();
this.streams.set(broadcast, broadcast);
const dispatcher = new StreamDispatcher(this, broadcast, options);
dispatcher.on('end', () => this.destroyStream(broadcast));
dispatcher.on('error', () => this.destroyStream(broadcast));
dispatcher.on('speaking', value => this.voiceConnection.setSpeaking(value));
broadcast.registerDispatcher(dispatcher);
return dispatcher;
}
} }
module.exports = AudioPlayer; module.exports = AudioPlayer;

View File

@@ -12,6 +12,8 @@ client.login(auth.token).then(() => console.log('logged')).catch(console.error);
const connections = new Map(); const connections = new Map();
let broadcast;
client.on('message', m => { client.on('message', m => {
if (!m.guild) return; if (!m.guild) return;
if (m.content.startsWith('/join')) { if (m.content.startsWith('/join')) {
@@ -39,6 +41,13 @@ client.on('message', m => {
} }
doQueue(connData); doQueue(connData);
} }
} else if (m.content.startsWith('#eval') && m.author.id === '66564597481480192') {
try {
const com = eval(m.content.split(' ').slice(1).join(' '));
m.channel.sendMessage(`\`\`\`\n${com}\`\`\``);
} catch (e) {
m.channel.sendMessage(`\`\`\`\n${e}\`\`\``);
}
} }
}); });