Refactor cleanup playbackmanager and document code

This commit is contained in:
Manuel 2022-08-07 17:35:17 +02:00
parent 00a66a668f
commit 6c34025dae
3 changed files with 238 additions and 155 deletions

View File

@ -1,6 +1,7 @@
const discordclientmanager = require("./discordclientmanager"); const discordclientmanager = require("./discordclientmanager");
const CONFIG = require("../config.json"); const CONFIG = require("../config.json");
const { secondsToHms, ticksToSeconds } = require("./util"); const { secondsToHms, ticksToSeconds } = require("./util");
const log = require("loglevel");
function getProgressString(percent) { function getProgressString(percent) {
// the min with of the discord window allows for this many chars // the min with of the discord window allows for this many chars
@ -58,7 +59,7 @@ class InterActivePlayMessage {
onStop, onStop,
onNext, onNext,
onRepeat, onRepeat,
playlistLenth playlistLenth,
) { ) {
this.ticksLength = ticksLength; this.ticksLength = ticksLength;
var exampleEmbed = { var exampleEmbed = {
@ -66,7 +67,7 @@ class InterActivePlayMessage {
title: "Now Playing", title: "Now Playing",
url: itemURL, url: itemURL,
description: `\`\`${getMaxWidthString(title)}\`\` by \`\`${getMaxWidthString( description: `\`\`${getMaxWidthString(title)}\`\` by \`\`${getMaxWidthString(
artist artist,
)}\`\``, )}\`\``,
thumbnail: { thumbnail: {
url: imageURL, url: imageURL,
@ -78,7 +79,7 @@ class InterActivePlayMessage {
exampleEmbed.fields.push({ exampleEmbed.fields.push({
name: getProgressString(0 / this.ticksLength), name: getProgressString(0 / this.ticksLength),
value: `${secondsToHms(0)} / ${secondsToHms( value: `${secondsToHms(0)} / ${secondsToHms(
ticksToSeconds(this.ticksLength) ticksToSeconds(this.ticksLength),
)}`, )}`,
inline: false, inline: false,
}); });
@ -151,7 +152,7 @@ class InterActivePlayMessage {
this.musicplayermessage.embeds[0].fields[0] = { this.musicplayermessage.embeds[0].fields[0] = {
name: getProgressString(ticks / this.ticksLength), name: getProgressString(ticks / this.ticksLength),
value: `${secondsToHms(ticksToSeconds(ticks))} / ${secondsToHms( value: `${secondsToHms(ticksToSeconds(ticks))} / ${secondsToHms(
ticksToSeconds(this.ticksLength) ticksToSeconds(this.ticksLength),
)}`, )}`,
inline: false, inline: false,
}; };
@ -168,11 +169,21 @@ class InterActivePlayMessage {
itemURL, itemURL,
ticksLength, ticksLength,
playlistIndex, playlistIndex,
playlistLenth playlistLenth,
) { ) {
if (!this.musicplayermessage) {
log.error("Interactive play message was not found");
return;
}
if (this.musicplayermessage.embeds.length === 0) {
log.error("Interactive play message was unable to access embeds");
return;
}
this.musicplayermessage.embeds[0].url = itemURL; this.musicplayermessage.embeds[0].url = itemURL;
this.musicplayermessage.embeds[0].description = `\`\`${getMaxWidthString( this.musicplayermessage.embeds[0].description = `\`\`${getMaxWidthString(
title title,
)}\`\` by \`\`${getMaxWidthString(artist)}\`\``; )}\`\` by \`\`${getMaxWidthString(artist)}\`\``;
this.musicplayermessage.embeds[0].thumbnail = { url: imageURL }; this.musicplayermessage.embeds[0].thumbnail = { url: imageURL };
const indexOfPlaylistMessage = const indexOfPlaylistMessage =

View File

@ -1,6 +1,8 @@
const InterActivePlayMessage = require("./InterActivePlayMessage"); const InterActivePlayMessage = require("./InterActivePlayMessage");
const CONFIG = require("../config.json"); const CONFIG = require("../config.json");
const log = require("loglevel");
var iapm; var iapm;
var updateInterval; var updateInterval;
@ -83,6 +85,8 @@ function updateCurrentSongMessage(
playlistIndex, playlistIndex,
playlistLenth playlistLenth
) { ) {
log.log(iapm);
if (typeof iapm !== "undefined") { if (typeof iapm !== "undefined") {
iapm.updateCurrentSongMessage( iapm.updateCurrentSongMessage(
title, title,

View File

@ -5,7 +5,7 @@ const log = require("loglevel");
const { const {
getAudioDispatcher, getAudioDispatcher,
setAudioDispatcher, setAudioDispatcher
} = require("./dispachermanager"); } = require("./dispachermanager");
const { ticksToSeconds } = require("./util"); const { ticksToSeconds } = require("./util");
@ -19,6 +19,7 @@ var _disconnectOnFinish;
var _seek; var _seek;
const jellyfinClientManager = require("./jellyfinclientmanager"); const jellyfinClientManager = require("./jellyfinclientmanager");
const { VoiceConnection } = require("discord.js");
function streamURLbuilder(itemID, bitrate) { function streamURLbuilder(itemID, bitrate) {
// so the server transcodes. Seems appropriate as it has the source file.(doesnt yet work i dont know why) // so the server transcodes. Seems appropriate as it has the source file.(doesnt yet work i dont know why)
@ -45,19 +46,29 @@ function startPlaying(
disconnectOnFinish = _disconnectOnFinish disconnectOnFinish = _disconnectOnFinish
) { ) {
log.debug( log.debug(
"start playing ", "Start playing",
itemIDPlaylist[playlistIndex],
"with index",
playlistIndex, playlistIndex,
". of list: ", "of list with length of",
itemIDPlaylist, itemIDPlaylist.length,
" in a voiceconnection?: ", "in",
typeof voiceconnection !== "undefined" voiceconnection && voiceconnection.channel
? '"' +
voiceconnection.channel.name +
'" (' +
voiceconnection.channel.id +
")"
: "an unknown voice channel"
); );
isPaused = false; isPaused = false;
currentPlayingPlaylist = itemIDPlaylist; currentPlayingPlaylist = itemIDPlaylist;
currentPlayingPlaylistIndex = playlistIndex; currentPlayingPlaylistIndex = playlistIndex;
_disconnectOnFinish = disconnectOnFinish; _disconnectOnFinish = disconnectOnFinish;
_seek = seekTo * 1000; _seek = seekTo * 1000;
updatePlayMessage(); updatePlayMessage();
async function playasync() { async function playasync() {
const url = streamURLbuilder( const url = streamURLbuilder(
itemIDPlaylist[playlistIndex], itemIDPlaylist[playlistIndex],
@ -65,7 +76,7 @@ function startPlaying(
); );
setAudioDispatcher( setAudioDispatcher(
voiceconnection.play(url, { voiceconnection.play(url, {
seek: seekTo, seek: seekTo
}) })
); );
if (seekTo) { if (seekTo) {
@ -78,43 +89,35 @@ function startPlaying(
itemID: `${itemIDPlaylist[playlistIndex]}`, itemID: `${itemIDPlaylist[playlistIndex]}`,
canSeek: true, canSeek: true,
playSessionId: getPlaySessionId(), playSessionId: getPlaySessionId(),
playMethod: getPlayMethod(), playMethod: getPlayMethod()
}); });
} }
getAudioDispatcher().on("finish", () => { getAudioDispatcher().on("finish", () => {
// report playback stop and start the same index again
if (isRepeat) { if (isRepeat) {
log.debug( reportPlaybackStoppedAndStartPlaying(
"repeat and sending following payload as reportPlaybackStopped to the server: ", voiceconnection,
getStopPayload() currentPlayingPlaylistIndex
); );
jellyfinClientManager return;
.getJellyfinClient()
.reportPlaybackStopped(getStopPayload());
startPlaying(voiceconnection, undefined, currentPlayingPlaylistIndex, 0);
} else {
if (currentPlayingPlaylist.length < playlistIndex) {
if (disconnectOnFinish) {
stop(voiceconnection, currentPlayingPlaylist[playlistIndex - 1]);
} else {
stop(undefined, currentPlayingPlaylist[playlistIndex - 1]);
}
} else {
log.debug(
"repeat and sending following payload as reportPlaybackStopped to the server: ",
getStopPayload()
);
jellyfinClientManager
.getJellyfinClient()
.reportPlaybackStopped(getStopPayload());
startPlaying(
voiceconnection,
undefined,
currentPlayingPlaylistIndex + 1,
0
);
}
} }
if (currentPlayingPlaylist.length < playlistIndex) {
if (disconnectOnFinish) {
stop(voiceconnection, currentPlayingPlaylist[playlistIndex - 1]);
return;
}
stop(undefined, currentPlayingPlaylist[playlistIndex - 1]);
return;
}
// play the next song in the playlist
reportPlaybackStoppedAndStartPlaying(
voiceconnection,
currentPlayingPlaylistIndex + 1
);
}); });
} }
playasync().catch((rsn) => { playasync().catch((rsn) => {
@ -122,8 +125,41 @@ function startPlaying(
}); });
} }
/**
*
* @param {VoiceConnection} voiceconnection - The voiceConnection where the bot should play
* @param {number} playlistIndex - The target playlist index
* @param {any} disconnectOnFinish
*/
const reportPlaybackStoppedAndStartPlaying = (
voiceconnection,
playlistIndex,
disconnectOnFinish
) => {
const stopPayload = getStopPayload();
log.debug(
"Repeat and sending following payload as reportPlaybackStopped to the server: ",
stopPayload
);
jellyfinClientManager.getJellyfinClient().reportPlaybackStopped(stopPayload);
startPlaying(voiceconnection, undefined, playlistIndex, 0, disconnectOnFinish);
};
async function spawnPlayMessage(message) { async function spawnPlayMessage(message) {
log.debug("spawned Play Message?: ", typeof message !== "undefined"); if (!message.channel) {
log.error("Unable to send play message in channel");
log.debug(message);
return;
}
log.debug(
"Sending play message to channel",
message.channel.name,
"(" + message.channel.id + ")"
);
const itemIdDetails = await jellyfinClientManager const itemIdDetails = await jellyfinClientManager
.getJellyfinClient() .getJellyfinClient()
.getItem( .getItem(
@ -164,37 +200,46 @@ async function spawnPlayMessage(message) {
interactivemsghandler.startUpate(getPostitionTicks); interactivemsghandler.startUpate(getPostitionTicks);
} }
} catch (error) { } catch (error) {
console.error(error); log.error(error);
} }
} }
async function updatePlayMessage() { async function updatePlayMessage() {
if (getItemId() !== undefined) { const itemId = getItemId();
const itemIdDetails = await jellyfinClientManager
.getJellyfinClient()
.getItem(
jellyfinClientManager.getJellyfinClient().getCurrentUserId(),
getItemId()
);
const imageURL = await jellyfinClientManager
.getJellyfinClient()
.getImageUrl(itemIdDetails.AlbumId || getItemId(), { type: "Primary" });
try { if (!itemId) {
interactivemsghandler.updateCurrentSongMessage( return;
itemIdDetails.Name, }
itemIdDetails.Artists[0] || "VA",
imageURL, const jellyfinItemDetails = await jellyfinClientManager
`${jellyfinClientManager .getJellyfinClient()
.getJellyfinClient() .getItem(
.serverAddress()}/web/index.html#!/details?id=${itemIdDetails.AlbumId}`, jellyfinClientManager.getJellyfinClient().getCurrentUserId(),
itemIdDetails.RunTimeTicks, getItemId()
currentPlayingPlaylistIndex + 1, );
currentPlayingPlaylist.length
); const primaryAlbumCover = await jellyfinClientManager
} catch (exception) { .getJellyfinClient()
console.error(exception); .getImageUrl(jellyfinItemDetails.AlbumId || itemId, { type: "Primary" });
}
log.debug("Extracted primary Album cover url:", primaryAlbumCover);
try {
interactivemsghandler.updateCurrentSongMessage(
jellyfinItemDetails.Name,
jellyfinItemDetails.Artists[0] || "VA",
primaryAlbumCover,
`${jellyfinClientManager
.getJellyfinClient()
.serverAddress()}/web/index.html#!/details?id=${
jellyfinItemDetails.AlbumId
}`,
jellyfinItemDetails.RunTimeTicks,
currentPlayingPlaylistIndex + 1,
currentPlayingPlaylist.length
);
} catch (exception) {
log.error("Exception during updating the current song message:", exception);
} }
} }
@ -202,88 +247,90 @@ async function updatePlayMessage() {
* @param {Number} toSeek - where to seek in ticks * @param {Number} toSeek - where to seek in ticks
*/ */
function seek(toSeek = 0) { function seek(toSeek = 0) {
log.debug("seek to: ", toSeek); log.debug("Seeking to: ", toSeek);
if (getAudioDispatcher()) {
startPlaying(
undefined,
undefined,
undefined,
ticksToSeconds(toSeek),
_disconnectOnFinish
);
jellyfinClientManager
.getJellyfinClient()
.reportPlaybackProgress(getProgressPayload());
} else {
throw Error("No Song Playing");
}
}
/**
*
* @param {Array} itemID - array of itemIDs to be added
*/
function addTracks(itemID) {
log.debug("added track: ", itemID);
currentPlayingPlaylist = currentPlayingPlaylist.concat(itemID);
}
function nextTrack() { if (!getAudioDispatcher()) {
log.debug("nextTrack"); log.warn("Failed to seek because no song is playing.");
if (!currentPlayingPlaylist) {
throw Error("There is currently nothing playing");
} else if (currentPlayingPlaylistIndex + 1 >= currentPlayingPlaylist.length) {
throw Error("This is the Last song");
} }
log.debug( // start playing the same track but with a specified time
"sending following payload as reportPlaybackStopped to the server: ",
getStopPayload()
);
jellyfinClientManager
.getJellyfinClient()
.reportPlaybackStopped(getStopPayload());
startPlaying( startPlaying(
undefined, undefined,
undefined,
undefined,
ticksToSeconds(toSeek),
_disconnectOnFinish
);
// report change about playback progress to Jellyfin
jellyfinClientManager
.getJellyfinClient()
.reportPlaybackProgress(getProgressPayload());
}
/**
*
* @param {Array} trackItemIdsArray - array of itemIDs to be added
*/
function addTracks(trackItemIdsArray) {
currentPlayingPlaylist = currentPlayingPlaylist.concat(trackItemIdsArray);
log.debug(
"Added tracks of",
trackItemIdsArray.length,
"to the current playlist"
);
}
function nextTrack() {
log.debug("Going to the next track...");
if (!currentPlayingPlaylist) {
log.warn(
"Can't go to the next track, because there is currently nothing playing"
);
return;
}
if (currentPlayingPlaylistIndex + 1 >= currentPlayingPlaylist.length) {
log.warn(
"Can't go to next track, because the current playing song is the last song."
);
return;
}
reportPlaybackStoppedAndStartPlaying(
undefined, undefined,
currentPlayingPlaylistIndex + 1, currentPlayingPlaylistIndex + 1,
0,
_disconnectOnFinish _disconnectOnFinish
); );
} }
function previousTrack() { function previousTrack() {
log.debug("previousTrack"); log.debug("Going to the previous track...");
if (ticksToSeconds(getPostitionTicks()) < 10) {
if (!currentPlayingPlaylist) {
throw Error("There is currently nothing playing");
} else if (currentPlayingPlaylistIndex - 1 < 0) {
startPlaying(
undefined,
undefined,
currentPlayingPlaylistIndex,
0,
_disconnectOnFinish
);
throw Error("This is the First song");
}
log.debug( if (ticksToSeconds(getPostitionTicks()) > 10) {
"sending following payload as reportPlaybackStopped to the server: ", return;
getStopPayload()
);
jellyfinClientManager
.getJellyfinClient()
.reportPlaybackStopped(getStopPayload());
startPlaying(
undefined,
undefined,
currentPlayingPlaylistIndex - 1,
0,
_disconnectOnFinish
);
} }
// don't go to the previous track when nothing is playing
if (!currentPlayingPlaylist) {
log.warn(
"Can't go to the previous track, because there's currently nothing playing"
);
return;
}
if (currentPlayingPlaylistIndex - 1 < 0) {
log.warn(
"Can't go to the previous track, because this is the first track in the playlist"
);
return;
}
reportPlaybackStoppedAndStartPlaying(
undefined,
currentPlayingPlaylistIndex - 1,
_disconnectOnFinish
);
} }
/** /**
@ -315,32 +362,54 @@ function stop(disconnectVoiceConnection, itemId = getItemId()) {
} }
function pause() { function pause() {
log.debug("pause"); log.debug("Pausing the current track...");
isPaused = true; isPaused = true;
// report to Jellyfin that the client has paused the track
jellyfinClientManager jellyfinClientManager
.getJellyfinClient() .getJellyfinClient()
.reportPlaybackProgress(getProgressPayload()); .reportPlaybackProgress(getProgressPayload());
// pause the track in the audio dispatcher
getAudioDispatcher().pause(true); getAudioDispatcher().pause(true);
} }
function resume() { function resume() {
log.debug("resume"); log.debug("Resuming playback of the current track...");
isPaused = false; isPaused = false;
// report to Jellyfin that the client has resumed playback
jellyfinClientManager jellyfinClientManager
.getJellyfinClient() .getJellyfinClient()
.reportPlaybackProgress(getProgressPayload()); .reportPlaybackProgress(getProgressPayload());
// resume playback in the audio dispatcher
getAudioDispatcher().resume(); getAudioDispatcher().resume();
} }
/**
* Pauses the playback of the current track is playing or
* resumes the placback if the current track is paused
*/
function playPause() { function playPause() {
if (!getAudioDispatcher()) { const audioDispatcher = getAudioDispatcher();
throw Error("There is nothing Playing right now!");
if (!audioDispatcher) {
log.warn(
"Can't toggle the playback of the current song because there is nothing playing right now"
);
return;
} }
if (getAudioDispatcher().paused) {
if (audioDispatcher.paused) {
log.debug("Resuming playback because the current track is paused...");
resume(); resume();
} else { return;
pause();
} }
log.debug("Pausing the playback because the current track is playing...");
pause();
} }
function getPostitionTicks() { function getPostitionTicks() {
@ -359,9 +428,9 @@ function getPlayMethod() {
function getRepeatMode() { function getRepeatMode() {
if (isRepeat) { if (isRepeat) {
return "RepeatOne"; return "RepeatOne";
} else {
return "RepeatNone";
} }
return "RepeatNone";
} }
function getPlaylistItemId() { function getPlaylistItemId() {
@ -369,7 +438,7 @@ function getPlaylistItemId() {
} }
function getPlaySessionId() { function getPlaySessionId() {
// i think its just a number which you dont need to retrieve but need to report // TODO: generate a unique identifier for identification at Jellyfin. This may cause conflicts when running multiple bots on the same Jellyfin server.
return "ae2436edc6b91b11d72aeaa67f84e0ea"; return "ae2436edc6b91b11d72aeaa67f84e0ea";
} }
@ -378,8 +447,8 @@ function getNowPLayingQueue() {
{ {
Id: getItemId(), Id: getItemId(),
// as I curently dont support Playlists // as I curently dont support Playlists
PlaylistItemId: getPlaylistItemId(), PlaylistItemId: getPlaylistItemId()
}, }
]; ];
} }
@ -435,20 +504,19 @@ function getProgressPayload() {
PositionTicks: getPostitionTicks(), PositionTicks: getPostitionTicks(),
RepeatMode: getRepeatMode(), RepeatMode: getRepeatMode(),
VolumeLevel: getVolumeLevel(), VolumeLevel: getVolumeLevel(),
EventName: "pauseplayupdate", EventName: "pauseplayupdate"
}; };
return payload; return payload;
} }
function getStopPayload() { function getStopPayload() {
const payload = { return {
userId: jellyfinClientManager.getJellyfinClient().getCurrentUserId(), userId: jellyfinClientManager.getJellyfinClient().getCurrentUserId(),
itemId: getItemId(), itemId: getItemId(),
sessionID: getPlaySessionId(), sessionID: getPlaySessionId(),
playSessionId: getPlaySessionId(), playSessionId: getPlaySessionId(),
positionTicks: getPostitionTicks(), positionTicks: getPostitionTicks()
}; };
return payload;
} }
module.exports = { module.exports = {
@ -463,5 +531,5 @@ module.exports = {
previousTrack, previousTrack,
addTracks, addTracks,
getPostitionTicks, getPostitionTicks,
spawnPlayMessage, spawnPlayMessage
}; };