Skip to content

Commit 3b2cb5d

Browse files
committed
add: native Deezer and BandCamp playback
This commit adds native playback for Deezer and BandCamp, removing the need for FFmpeg.
1 parent 1f3e734 commit 3b2cb5d

File tree

6 files changed

+54
-17
lines changed

6 files changed

+54
-17
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ Performant LavaLink replacement written in Node.js.
1616
- [`prism-media`](https://npmjs.com/package/prism-media)
1717
- [`opusscript`](https://npmjs.com/package/opusscript) or [`@discordjs/opus`](https://npmjs.com/package/@discordjs/opus)
1818
- [`libsodium-wrappers`](https://npmjs.com/package/libsodium-wrappers) or [`sodium-native`](https://npmjs.com/package/sodium-native) or [`tweetnacl`](https://npmjs.com/package/tweetnacl)
19+
- [`ffmpeg`](https://ffmpeg.org/) or [`avconv`](https://libav.org/) or [`ffmpeg-static`](https://npmjs.com/package/ffmpeg-static)
20+
21+
> [!NOTE]
22+
> For most sources FFmpeg isn't required. It is current required for timescale, seek and endTime filter. Required for `local` and `http` sources.
1923
2024
## Installation
2125

config.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,8 @@ export default {
162162
},
163163
audio: {
164164
quality: 'high',
165-
encryption: 'xsalsa20_poly1305_lite'
165+
encryption: 'xsalsa20_poly1305_lite',
166+
resamplingQuality: 'best' // best, medium, fastest, zero order holder, linear
166167
},
167168
voiceReceive: {
168169
type: 'pcm', // pcm, opus

src/filters.js

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -177,19 +177,6 @@ class Filters {
177177
getResource(decodedTrack, streamInfo, realTime, currentStream) {
178178
return new Promise(async (resolve) => {
179179
try {
180-
if (decodedTrack.sourceName === 'deezer') {
181-
debugLog('retrieveStream', 4, { type: 2, sourceName: decodedTrack.sourceName, query: decodedTrack.title, message: 'Filtering does not support Deezer platform.' })
182-
183-
return resolve({
184-
status: 1,
185-
exception: {
186-
message: 'Filtering does not support Deezer platform',
187-
severity: 'fault',
188-
cause: 'Unimplemented feature.'
189-
}
190-
})
191-
}
192-
193180
const startTime = this.filters.find((filter) => filter.name === 'seek')?.data
194181
const endTime = this.filters.find((filter) => filter.name === 'endTime')?.data
195182

@@ -234,6 +221,19 @@ class Filters {
234221
resolve({ stream })
235222
}
236223
} else {
224+
if (decodedTrack.sourceName === 'deezer') {
225+
debugLog('retrieveStream', 4, { type: 2, sourceName: decodedTrack.sourceName, query: decodedTrack.title, message: 'Filtering does not support Deezer platform.' })
226+
227+
return resolve({
228+
status: 1,
229+
exception: {
230+
message: 'Non-native filtering does not support Deezer platform',
231+
severity: 'fault',
232+
cause: 'Unimplemented feature.'
233+
}
234+
})
235+
}
236+
237237
const ffmpeg = new prism.FFmpeg({
238238
args: [
239239
'-loglevel', '0',

src/sources/bandcamp.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ async function retrieveStream(uri, title) {
183183
return {
184184
url: streamURL[0],
185185
protocol: 'https',
186-
format: 'arbitrary'
186+
format: 'mp3'
187187
}
188188
}
189189

src/sources/deezer.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ async function retrieveStream(identifier, title) {
286286
return {
287287
url: streamData.data[0].media[0].sources[0].url,
288288
protocol: 'https',
289-
format: 'arbitrary',
289+
format: streamData.data[0].media[0].format.startsWith('MP3') ? 'mp3' : 'flac',
290290
additionalData: trackInfo
291291
}
292292
}

src/voice/utils.js

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,18 @@
1+
import prism from 'prism-media'
2+
import lame from '@flat/lame' /* libmp3lame bindings */
3+
import SampleRate from 'node-libsamplerate' /* libsamplerate bindings */
4+
15
import config from '../../config.js'
26
import constants from '../../constants.js'
37

4-
import prism from 'prism-media'
8+
let resamplingQuality = null
9+
switch (config.audio.resamplingQuality) {
10+
case 'best': resamplingQuality = SampleRate.SRC_SINC_BEST_QUALITY; break
11+
case 'medium': resamplingQuality = SampleRate.SRC_SINC_MEDIUM_QUALITY; break
12+
case 'fastest': resamplingQuality = SampleRate.SRC_ZERO_ORDER_HOLD; break
13+
case 'zero order holder': resamplingQuality = SampleRate.SRC_ZERO_ORDER_HOLD; break
14+
case 'linear': resamplingQuality = SampleRate.SRC_LINEAR; break
15+
}
516

617
class NodeLinkStream {
718
constructor(stream, pipes, ffmpegState) {
@@ -95,6 +106,7 @@ function isDecodedInternally(stream, type) {
95106
case 'webm/opus':
96107
case 'ogg/opus': return 3 + 1 + (stream.ffmpegState === 2 ? -2 : 0)
97108
case 'wav': return 2 + 1 + (stream.ffmpegState === 2 ? -2 : 0)
109+
case 'mp3': return 3 + 1 + (stream.ffmpegState === 2 ? -2 : 0)
98110
default: return false
99111
}
100112
}
@@ -126,6 +138,26 @@ function createAudioResource(stream, type, additionalPipes = [], ffmpegState = f
126138
], ffmpegState)
127139
}
128140

141+
if (type === 'mp3') {
142+
return new NodeLinkStream(stream, [
143+
new lame.Decoder(),
144+
new SampleRate.SampleRate({
145+
type: resamplingQuality,
146+
channels: 2,
147+
fromRate: 44100,
148+
fromDepth: 16,
149+
toRate: constants.opus.samplingRate,
150+
toDepth: 16
151+
}),
152+
...additionalPipes,
153+
new prism.opus.Encoder({
154+
rate: constants.opus.samplingRate,
155+
channels: constants.opus.channels,
156+
frameSize: constants.opus.frameSize
157+
})
158+
], ffmpegState)
159+
}
160+
129161
const ffmpeg = new prism.FFmpeg({
130162
args: [
131163
'-loglevel', '0',

0 commit comments

Comments
 (0)