Skip to content

Commit

Permalink
Fix music playback
Browse files Browse the repository at this point in the history
  • Loading branch information
ebma committed May 10, 2023
1 parent d951888 commit 5c11ed9
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 50 deletions.
4 changes: 3 additions & 1 deletion src/libs/MusicPlayerManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { trackError } from "../utils/trackError"
import MusicPlayer from "./MusicPlayer"
import MusicPlayerObserver from "./MusicPlayerObserver"
import StreamManager from "./StreamManager"
import { joinVoiceChannel } from "@discordjs/voice"
import { entersState, joinVoiceChannel, VoiceConnectionStatus } from "@discordjs/voice"

export class MusicPlayerManager {
private musicPlayerMap: { [key in GuildID]: MusicPlayer } = {}
Expand All @@ -19,6 +19,8 @@ export class MusicPlayerManager {
guildId: channel.guild.id,
adapterCreator: channel.guild.voiceAdapterCreator,
})
await entersState(connection, VoiceConnectionStatus.Ready, 30_000)

const musicPlayer = new MusicPlayer(new StreamManager(connection))
this.musicPlayerMap[guildID] = musicPlayer

Expand Down
109 changes: 65 additions & 44 deletions src/libs/StreamManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,59 +4,77 @@ import { PartialObserver, Subject } from "rxjs"
import { Readable } from "stream"
import Youtube from "../libs/Youtube"
import { trackError } from "../utils/trackError"
import { AudioPlayer, createAudioResource, StreamType, VoiceConnection } from "@discordjs/voice"
import {
AudioPlayer,
AudioPlayerStatus,
AudioResource,
createAudioPlayer,
createAudioResource,
entersState,
NoSubscriberBehavior,
StreamType,
VoiceConnection,
} from "@discordjs/voice"

class StreamManager {
private voiceConnection: VoiceConnection
private musicDispatcher?: AudioPlayer
private audioResource?: AudioResource
private player: AudioPlayer
private streamSource?: Readable | Track
private volume: number = 0.1
private subject: Subject<StreamManagerObservableMessage>

constructor(voiceConnection: VoiceConnection) {
this.voiceConnection = voiceConnection
this.subject = new Subject()
this.player = createAudioPlayer({
behaviors: {
noSubscriber: NoSubscriberBehavior.Stop,
maxMissedFrames: Math.round(5000 / 20),
},
})
this.voiceConnection.subscribe(this.player)
}

setVolume(volume: number) {
if (volume < 0 || volume > 100) {
throw new Error("Invalid volume! Choose a number between 0-100%")
}
this.volume = volume / 100
if (this.musicDispatcher) {
// this.streamSource.setVolume(this.volume)
if (this.audioResource) {
this.audioResource.volume?.setVolume(this.volume)
}
}

getVolume() {
return this.volume * 100
return (this.audioResource?.volume?.volume ?? this.volume) * 100
}

get paused(): boolean {
return Boolean(this.musicDispatcher && this.musicDispatcher.state.status === "paused")
return Boolean(this.player.state.status === "paused")
}

get playing(): boolean {
return Boolean(this.musicDispatcher)
return Boolean(this.audioResource)
}

pause() {
if (!this.musicDispatcher) {
if (this.player.state.status === "idle") {
throw new Error("Can't pause before starting to play something.")
} else if (this.musicDispatcher.state.status === "paused") {
} else if (this.player.state.status === "paused") {
throw new Error("Stream paused already.")
} else {
this.musicDispatcher.pause(true)
this.player.pause(true)
}
}

resume() {
if (!this.musicDispatcher) {
if (this.player.state.status === "idle") {
throw new Error("Can't resume before starting to play something.")
} else if (!(this.musicDispatcher.state.status === "paused")) {
} else if (this.audioResource.audioPlayer.state.status === "playing") {
throw new Error("Stream running already.")
} else {
this.musicDispatcher.unpause()
this.player.unpause()
}
}

Expand All @@ -69,8 +87,8 @@ class StreamManager {
if (this.streamSource instanceof Readable) this.streamSource.unpipe() // unpipe because otherwise the stream will not work with the next voice connection
const audioResource = createAudioResource(result, { inlineVolume: true, inputType: StreamType.OggOpus })
audioResource.volume?.setVolume(vol)
this.musicDispatcher.play(audioResource)
audioResource.audioPlayer
this.player.play(audioResource)
this.player
.on("stateChange", (oldState, newState) => {
if (oldState.status === "playing" && newState.status === "idle") {
if (this.streamSource) {
Expand All @@ -89,20 +107,20 @@ class StreamManager {

async play(input: Track | Readable, trackPaused: boolean = false) {
try {
let dispatcher: AudioPlayer = null
let player: AudioPlayer = null
if (input instanceof Readable) {
dispatcher = await this.playReadable(input)
player = await this.playReadable(input)
} else {
if (input.source === "radio") {
this.musicDispatcher = null // set to null before because of weird behaviour of 'dispatcher.end/finish'
dispatcher = await this.playUnknown(input)
// this.musicDispatcher = null // set to null before because of weird behaviour of 'dispatcher.end/finish'
player = await this.playUnknown(input)
} else {
dispatcher = await this.playYoutube(input)
player = await this.playYoutube(input)
}
}

if (trackPaused) dispatcher.pause()
this.musicDispatcher = dispatcher
if (trackPaused) player.pause()
this.player = player
} catch (error) {
trackError(error)
}
Expand All @@ -112,26 +130,29 @@ class StreamManager {
const source = await Youtube.createReadableStreamFor(input)
this.streamSource = source

const audioResource = createAudioResource(source, { inlineVolume: true, inputType: StreamType.OggOpus })
const audioResource = createAudioResource(source, { inlineVolume: true })
audioResource.volume?.setVolume(this.volume)
this.musicDispatcher.play(audioResource)
this.player.play(audioResource)
entersState(this.player, AudioPlayerStatus.Playing, 5000000).catch((error) => {
console.error("entersstate error", error)
})

audioResource.audioPlayer
this.player
.on("debug", (info: any) => this.subject.next({ type: "debug", data: info }))
.on("stateChange", (oldState, newState) => {
if (oldState.status === "playing" && newState.status === "idle") {
if (this.musicDispatcher === audioResource.audioPlayer) {
if (this.player === audioResource.audioPlayer) {
this.streamSource = null
this.musicDispatcher = null
// this.player = null
this.subject.next({ type: "finish" })
}
}
})
.on("error", (error: any) => {
if (this.musicDispatcher === audioResource.audioPlayer) {
if (this.player === audioResource.audioPlayer) {
if (source instanceof Readable) source.destroy()
this.streamSource = null
this.musicDispatcher = null
// this.player = null
this.subject.next({ type: "error", data: error && error.message ? error.message : error })
}
trackError(error, "StreamManager.playYoutube error")
Expand All @@ -151,25 +172,25 @@ class StreamManager {

const audioResource = createAudioResource(stream, { inlineVolume: true, inputType: StreamType.OggOpus })
audioResource.volume?.setVolume(this.volume)
this.musicDispatcher.play(audioResource)
this.player.play(audioResource)

audioResource.audioPlayer
this.player
.on("debug", (info: any) => this.subject.next({ type: "debug", data: info }))
.on("stateChange", (oldState, newState) => {
if (oldState.status === "playing" && newState.status === "idle") {
if (this.musicDispatcher === audioResource.audioPlayer) {
if (this.player === audioResource.audioPlayer) {
this.streamSource = null
this.musicDispatcher = null
// this.player = null
this.subject.next({ type: "finish" })
}
} else if (oldState.status === "playing") {
this.subject.next({ type: "start" })
}
})
.on("error", (error: any) => {
if (this.musicDispatcher === audioResource.audioPlayer) {
if (this.player === audioResource.audioPlayer) {
this.streamSource = null
this.musicDispatcher = null
// this.player = null
this.subject.next({ type: "error", data: error && error.message ? error.message : error })
}
trackError(error, "StreamManager.playUnknown error")
Expand All @@ -181,26 +202,26 @@ class StreamManager {
this.streamSource = input
const audioResource = createAudioResource(input, { inlineVolume: true, inputType: StreamType.OggOpus })
audioResource.volume?.setVolume(this.volume)
this.musicDispatcher.play(audioResource)
this.player.play(audioResource)

this.musicDispatcher
this.player
.on("debug", (info: any) => this.subject.next({ type: "debug", data: info }))
.on("stateChange", (oldState, newState) => {
if (oldState.status === "playing" && newState.status === "idle") {
if (this.musicDispatcher === audioResource.audioPlayer) {
if (this.player === audioResource.audioPlayer) {
this.streamSource = null
this.musicDispatcher = null
// this.player = null
this.subject.next({ type: "finish" })
}
} else if (oldState.status === "playing") {
this.subject.next({ type: "start" })
}
})
.on("error", (error: any) => {
if (this.musicDispatcher === audioResource.audioPlayer) {
if (this.player === audioResource.audioPlayer) {
input.destroy()
this.streamSource = null
this.musicDispatcher = null
// this.player = null
this.subject.next({ type: "error", data: error && error.message ? error.message : error })
}
trackError(error, "StreamManager.playReadable error")
Expand All @@ -218,14 +239,14 @@ class StreamManager {
}

stop() {
if (this.musicDispatcher && this.musicDispatcher.state.status !== "idle") {
this.musicDispatcher.stop()
if (this.player.state.status !== "idle") {
this.player.stop()
}
}

disconnect() {
this.stop()
this.musicDispatcher = null
this.player = null
this.voiceConnection.disconnect()
}
}
Expand Down
9 changes: 4 additions & 5 deletions src/libs/video-download.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import ytdlDiscordWrapper from "discord-ytdl-core"
import ytdl from "ytdl-core"
import { HttpsProxyAgent } from "https-proxy-agent";
import _ from "lodash"

Expand All @@ -25,12 +25,11 @@ export function downloadVideoWithProxy(url: string, seek?: number) {
try {
const agent = new HttpsProxyAgent(proxyAddress)

const stream = ytdlDiscordWrapper(url, {
// encoderArgs: ["-af", "bass=g=5,dynaudnorm=f=200"],
const stream = ytdl (url, {
filter: "audioonly",
highWaterMark: 1 << 25,
opusEncoded: true,
seek
quality: "highestaudio",
begin: seek
})
return stream
} catch (error) {
Expand Down

0 comments on commit 5c11ed9

Please sign in to comment.