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 CONFIG = require("../config.json");
const { secondsToHms, ticksToSeconds } = require("./util");
const log = require("loglevel");
function getProgressString(percent) {
// the min with of the discord window allows for this many chars
@ -58,7 +59,7 @@ class InterActivePlayMessage {
onStop,
onNext,
onRepeat,
playlistLenth
playlistLenth,
) {
this.ticksLength = ticksLength;
var exampleEmbed = {
@ -66,7 +67,7 @@ class InterActivePlayMessage {
title: "Now Playing",
url: itemURL,
description: `\`\`${getMaxWidthString(title)}\`\` by \`\`${getMaxWidthString(
artist
artist,
)}\`\``,
thumbnail: {
url: imageURL,
@ -78,7 +79,7 @@ class InterActivePlayMessage {
exampleEmbed.fields.push({
name: getProgressString(0 / this.ticksLength),
value: `${secondsToHms(0)} / ${secondsToHms(
ticksToSeconds(this.ticksLength)
ticksToSeconds(this.ticksLength),
)}`,
inline: false,
});
@ -151,7 +152,7 @@ class InterActivePlayMessage {
this.musicplayermessage.embeds[0].fields[0] = {
name: getProgressString(ticks / this.ticksLength),
value: `${secondsToHms(ticksToSeconds(ticks))} / ${secondsToHms(
ticksToSeconds(this.ticksLength)
ticksToSeconds(this.ticksLength),
)}`,
inline: false,
};
@ -168,11 +169,21 @@ class InterActivePlayMessage {
itemURL,
ticksLength,
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].description = `\`\`${getMaxWidthString(
title
title,
)}\`\` by \`\`${getMaxWidthString(artist)}\`\``;
this.musicplayermessage.embeds[0].thumbnail = { url: imageURL };
const indexOfPlaylistMessage =

View File

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

View File

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