From e019f4c6e55af3075d7bc47d6ae428b5403a66e1 Mon Sep 17 00:00:00 2001 From: KGT1 Date: Thu, 24 Sep 2020 13:34:39 +0200 Subject: [PATCH] add playlist support --- src/messagehandler.js | 23 ++++-- src/playbackmanager.js | 159 +++++++++++++++++++++++++++++----------- src/websockethandler.js | 24 +++++- 3 files changed, 154 insertions(+), 52 deletions(-) diff --git a/src/messagehandler.js b/src/messagehandler.js index cf92bb1..bf3c5d1 100644 --- a/src/messagehandler.js +++ b/src/messagehandler.js @@ -121,7 +121,7 @@ async function playThis (message) { discordClient.user.client.voice.connections.forEach((element) => { songPlayMessage(message, argument); - playbackmanager.startPlaying(element, itemID, 0, isSummendByPlay); + playbackmanager.startPlaying(element, [itemID], 0, 0, isSummendByPlay); }); } @@ -149,19 +149,16 @@ function handleChannelMessage (message) { .setDescription("<:wave:757938481585586226> " + desc); message.channel.send(vcJoin); } else if ((message.content.startsWith(CONFIG["discord-prefix"] + "pause")) || (message.content.startsWith(CONFIG["discord-prefix"] + "resume"))) { - if (getAudioDispatcher() !== undefined) { + try { playbackmanager.playPause(); const noPlay = new Discord.MessageEmbed() .setColor(0xff0000) .setTitle("<:play_pause:757940598106882049> " + "Paused/Resumed.") .setTimestamp(); message.channel.send(noPlay); - } else { - const noPlay = new Discord.MessageEmbed() - .setColor(0xff0000) - .setTitle("<:x:757935515445231651> " + "There is nothing Playing right now!") - .setTimestamp(); - message.channel.send(noPlay); + } catch (error) { + const errorMessage = getDiscordEmbedError(error); + message.channel.send(errorMessage); } } else if (message.content.startsWith(CONFIG["discord-prefix"] + "play")) { if (discordClient.user.client.voice.connections.size < 1) { @@ -187,6 +184,13 @@ function handleChannelMessage (message) { const errorMessage = getDiscordEmbedError(error); message.channel.send(errorMessage); } + } else if (message.content.startsWith(CONFIG["discord-prefix"] + "skip")) { + try { + playbackmanager.nextTrack() + } catch (error) { + const errorMessage = getDiscordEmbedError(error); + message.channel.send(errorMessage); + } } else if (message.content.startsWith(CONFIG["discord-prefix"] + "help")) { /* eslint-disable quotes */ const reply = new Discord.MessageEmbed() @@ -207,6 +211,9 @@ function handleChannelMessage (message) { }, { name: `${CONFIG["discord-prefix"]}seek`, value: "Where to Seek to in seconds or MM:SS" + }, { + name: `${CONFIG["discord-prefix"]}skip`, + value: "Skip this Song" }, { name: `${CONFIG["discord-prefix"]}help`, value: "Display this help message" diff --git a/src/playbackmanager.js b/src/playbackmanager.js index 7c5f5cd..26b9041 100644 --- a/src/playbackmanager.js +++ b/src/playbackmanager.js @@ -7,132 +7,201 @@ const { ticksToSeconds } = require("./util"); -var currentPlayingItemId; + +//this whole thing should be a class but its probably too late now. + +var currentPlayingPlaylist; +var currentPlayingPlaylistIndex; var progressInterval; var isPaused; +var isRepeat; var _disconnectOnFinish; var _seek; const jellyfinClientManager = require("./jellyfinclientmanager"); -function streamURLbuilder (itemID, bitrate) { + +function streamURLbuilder(itemID, bitrate) { // so the server transcodes. Seems appropriate as it has the source file. const supportedCodecs = "opus"; const supportedContainers = "ogg,opus"; return `${jellyfinClientManager.getJellyfinClient().serverAddress()}/Audio/${itemID}/universal?UserId=${jellyfinClientManager.getJellyfinClient().getCurrentUserId()}&DeviceId=${jellyfinClientManager.getJellyfinClient().deviceId()}&MaxStreamingBitrate=${bitrate}&Container=${supportedContainers}&AudioCodec=${supportedCodecs}&api_key=${jellyfinClientManager.getJellyfinClient().accessToken()}&TranscodingContainer=ts&TranscodingProtocol=hls`; } -function startPlaying (voiceconnection = discordclientmanager.getDiscordClient().user.client.voice.connections.first(), itemID = currentPlayingItemId, seekTo, disconnectOnFinish = _disconnectOnFinish) { +function startPlaying(voiceconnection = discordclientmanager.getDiscordClient().user.client.voice.connections.first(), itemIDPlaylist = currentPlayingPlaylist, playlistIndex = currentPlayingPlaylistIndex, seekTo, disconnectOnFinish = _disconnectOnFinish) { isPaused = false; - currentPlayingItemId = itemID; + currentPlayingPlaylist = itemIDPlaylist; + currentPlayingPlaylistIndex = playlistIndex; _disconnectOnFinish = disconnectOnFinish; _seek = seekTo * 1000; - async function playasync () { - const url = streamURLbuilder(itemID, voiceconnection.channel.bitrate); - setAudioDispatcher(voiceconnection.play(url, { seek: seekTo })); + async function playasync() { + const url = streamURLbuilder(itemIDPlaylist[playlistIndex], voiceconnection.channel.bitrate); + setAudioDispatcher(voiceconnection.play(url, { + seek: seekTo + })); if (seekTo) { jellyfinClientManager.getJellyfinClient().reportPlaybackProgress(getProgressPayload()); } else { - jellyfinClientManager.getJellyfinClient().reportPlaybackStart({ userID: `${jellyfinClientManager.getJellyfinClient().getCurrentUserId()}`, itemID: `${itemID}`, canSeek: true, playSessionId: getPlaySessionId(), playMethod: getPlayMethod() }); + jellyfinClientManager.getJellyfinClient().reportPlaybackStart({ + userID: `${jellyfinClientManager.getJellyfinClient().getCurrentUserId()}`, + itemID: `${itemIDPlaylist[playlistIndex]}`, + canSeek: true, + playSessionId: getPlaySessionId(), + playMethod: getPlayMethod() + }); } getAudioDispatcher().on("finish", () => { - if (disconnectOnFinish) { - stop(voiceconnection); + if (currentPlayingPlaylist.length < playlistIndex) { + console.log("PLAYLIST END") + + if (disconnectOnFinish) { + stop(voiceconnection, currentPlayingPlaylist[playlistIndex - 1]); + }else { + stop(undefined, currentPlayingPlaylist[playlistIndex - 1]); + } + } else { - stop(); + startPlaying(voiceconnection, itemIDPlaylist, currentPlayingPlaylistIndex+1, 0) } }); } - playasync().catch((rsn) => { console.error(rsn); }); + playasync().catch((rsn) => { + console.error(rsn); + }); } /** * @param {Number} toSeek - where to seek in ticks */ -function seek (toSeek = 0) { +function seek(toSeek = 0) { if (getAudioDispatcher()) { - startPlaying(undefined, undefined, ticksToSeconds(toSeek), _disconnectOnFinish); + startPlaying(undefined, undefined, undefined, ticksToSeconds(toSeek), _disconnectOnFinish); jellyfinClientManager.getJellyfinClient().reportPlaybackProgress(getProgressPayload()); } else { throw Error("No Song Playing"); } } +function nextTrack(){ + //console.log(currentPlayingPlaylistIndex + 1, currentPlayingPlaylist.length); + if(!(currentPlayingPlaylist)){ + throw Error("There is currently nothing playing"); + }else if(currentPlayingPlaylistIndex + 1 >= currentPlayingPlaylist.length){ + throw Error("This is the Last song"); + } + startPlaying(undefined, undefined, currentPlayingPlaylistIndex + 1, 0, _disconnectOnFinish) +} + +function previousTrack(){ + if(ticksToSeconds(getPostitionTicks())<10){ + console.log(currentPlayingPlaylistIndex , currentPlayingPlaylist.length); + 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"); + } + startPlaying(undefined, undefined, currentPlayingPlaylistIndex - 1, 0, _disconnectOnFinish) + } +} + /** * @param {Object=} disconnectVoiceConnection - Optional The voice Connection do disconnect from */ -function stop (disconnectVoiceConnection) { +function stop(disconnectVoiceConnection, itemId = getItemId()) { + console.log("im getting called"); + console isPaused = true; if (disconnectVoiceConnection) { disconnectVoiceConnection.disconnect(); } - jellyfinClientManager.getJellyfinClient().reportPlaybackStopped({ userId: jellyfinClientManager.getJellyfinClient().getCurrentUserId(), itemId: currentPlayingItemId, playSessionId: getPlaySessionId() }); - if (getAudioDispatcher()) { getAudioDispatcher().destroy(); } + jellyfinClientManager.getJellyfinClient().reportPlaybackStopped({ + userId: jellyfinClientManager.getJellyfinClient().getCurrentUserId(), + itemId: itemId, + playSessionId: getPlaySessionId() + }); + if (getAudioDispatcher()) { + getAudioDispatcher().destroy(); + } setAudioDispatcher(undefined); clearInterval(progressInterval); } -function pause () { + +function pause() { isPaused = true; jellyfinClientManager.getJellyfinClient().reportPlaybackProgress(getProgressPayload()); getAudioDispatcher().pause(true); } -function resume () { + +function resume() { isPaused = false; jellyfinClientManager.getJellyfinClient().reportPlaybackProgress(getProgressPayload()); getAudioDispatcher().resume(); } -function playPause () { - if (getAudioDispatcher().paused) { resume(); } else { pause(); } + +function playPause() { + if(!(getAudioDispatcher())){ + throw Error("There is nothing Playing right now!"); + } + if (getAudioDispatcher().paused) { + resume(); + } else { + pause(); + } } -function getPostitionTicks () { +function getPostitionTicks() { // this is very sketchy but i dont know how else to do it + console.log((_seek + getAudioDispatcher().streamTime - getAudioDispatcher().pausedTime) * 10000); return (_seek + getAudioDispatcher().streamTime - getAudioDispatcher().pausedTime) * 10000; } -function getPlayMethod () { +function getPlayMethod() { // TODO figure out how to figure this out - return 0; + return "Transcode"; } -function getRepeatMode () { - return "RepeatNone"; +function getRepeatMode() { + if(isRepeat){ + return "RepeatOne"; + }else{ + return "RepeatNone"; + } } -function getPlaylistItemId () { - // as I curently dont support Playlists - return "playlistItem0"; +function getPlaylistItemId() { + return getItemId(); } -function getPlaySessionId () { +function getPlaySessionId() { // i think its just a number which you dont need to retrieve but need to report return "ae2436edc6b91b11d72aeaa67f84e0ea"; } -function getNowPLayingQueue () { +function getNowPLayingQueue() { return [{ - Id: currentPlayingItemId, + Id: getItemId(), // as I curently dont support Playlists PlaylistItemId: getPlaylistItemId() }]; } -function getCanSeek () { +function getCanSeek() { return true; } -function getIsMuted () { +function getIsMuted() { return false; } -function getVolumeLevel () { +function getVolumeLevel() { return 100; } -function getItemId () { - return currentPlayingItemId; +function getItemId() { + return currentPlayingPlaylist[currentPlayingPlaylistIndex]; } -function getIsPaused () { +function getIsPaused() { // AudioDispacker Paused is to slow if (isPaused === undefined) { @@ -142,7 +211,11 @@ function getIsPaused () { return isPaused; } -function getProgressPayload () { +function setIsRepeat(arg){ + isRepeat=arg; +} + +function getProgressPayload() { const payload = { CanSeek: getCanSeek(), IsMuted: getIsMuted(), @@ -167,5 +240,9 @@ module.exports = { playPause, resume, pause, - seek -}; + seek, + setIsRepeat, + nextTrack, + previousTrack, + getPostitionTicks +}; \ No newline at end of file diff --git a/src/websockethandler.js b/src/websockethandler.js index 337a605..5396681 100644 --- a/src/websockethandler.js +++ b/src/websockethandler.js @@ -1,5 +1,6 @@ const jellyfinClientManager = require("./jellyfinclientmanager"); const playbackmanager = require("./playbackmanager"); +const { ticksToSeconds }= require("./util"); function openSocket () { jellyfinClientManager.getJellyfinClient().openWebSocket(); @@ -7,13 +8,14 @@ function openSocket () { { PlayableMediaTypes: "Audio", SupportsMediaControl: "True", - SupportedCommands: "Play,Playstate" + SupportedCommands: "SetRepeatMode,Play,Playstate" } ); jellyfinClientManager.getJellyfinEvents().on(jellyfinClientManager.getJellyfinClient(), "message", (type, data) => { + console.log(data); if (data.MessageType === "Play") { if (data.Data.PlayCommand === "PlayNow") { - playbackmanager.startPlaying(undefined, data.Data.ItemIds[data.Data.StartIndex || 0], 0, false); + playbackmanager.startPlaying(undefined, data.Data.ItemIds, data.Data.StartIndex || 0, 0, false); } } else if (data.MessageType === "Playstate") { if (data.Data.Command === "PlayPause") { @@ -21,7 +23,23 @@ function openSocket () { } else if (data.Data.Command === "Stop") { playbackmanager.stop(); } else if (data.Data.Command === "Seek") { - playbackmanager.seek(data.Data.SeekPositionTicks); + //because the server sends seek an privious track at same time so i have to do timing + setTimeout(async()=>{playbackmanager.seek(data.Data.SeekPositionTicks);},20) + } else if (data.Data.Command === "NextTrack") { + try { + playbackmanager.nextTrack(); + } catch (error) { + console.error(error); + } + } else if (data.Data.Command === "PreviousTrack") { + try{ + console.log(ticksToSeconds(playbackmanager.getPostitionTicks())<10,ticksToSeconds(playbackmanager.getPostitionTicks()),` (${playbackmanager.getPostitionTicks()})`," < ",10) + if(ticksToSeconds(playbackmanager.getPostitionTicks())<10){ + playbackmanager.previousTrack(); + } + }catch(error){ + console.error(error); + } } } });