hawk / commit
Added control to show the current playing song and a way to play/pause.
public/api.js | 36 ++++++++++++++++++++++++++++++++++-- public/index.html | 43 ++++++++++++++++++++++++++++++++++++++----- public/main.js | 27 ++++++++++++++++++++++++++- public/signin.js | 2 +- public/style.css | 23 +++++++++++++++++++++++ 5 files changed, 122 insertions(+), 9 deletions(-) diff --git a/public/api.js b/public/api.js index 3015aa5..8aad24a 100644 --- a/public/api.js +++ b/public/api.js @@ -8,7 +8,7 @@ async function getAlbums() { return albums; } - let accessToken = await getAccessToken(); + const accessToken = await getAccessToken(); const response = await fetch('https://api.spotify.com/v1/me/albums', { headers: { Authorization: 'Bearer ' + accessToken @@ -28,7 +28,7 @@ async function getTracks(albumId) { return tracks; } - let accessToken = await getAccessToken(); + const accessToken = await getAccessToken(); const response = await fetch(`https://api.spotify.com/v1/albums/${albumId}/tracks`, { headers: { Authorization: 'Bearer ' + accessToken @@ -53,3 +53,35 @@ async function playTrack(trackId) { }) }); } + +async function getPlayingTrack() { + const accessToken = await getAccessToken(); + const response = await fetch("https://api.spotify.com/v1/me/player", { + headers: { + Authorization: 'Bearer ' + accessToken + } + }); + + const data = await response.json(); + return data; +} + +async function play() { + const accessToken = await getAccessToken(); + await fetch("https://api.spotify.com/v1/me/player/play", { + method: "PUT", + headers: { + Authorization: 'Bearer ' + accessToken + } + }); +} + +async function pause() { + const accessToken = await getAccessToken(); + await fetch("https://api.spotify.com/v1/me/player/pause", { + method: "PUT", + headers: { + Authorization: 'Bearer ' + accessToken + } + }); +} \ No newline at end of file diff --git a/public/index.html b/public/index.html index 34875ae..a383007 100644 --- a/public/index.html +++ b/public/index.html @@ -6,6 +6,8 @@ <title>Hawk</title> <link rel="icon" type="image/png" href="favicon.png"> <link rel="stylesheet" href="style.css"> + <link rel="stylesheet" + href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" /> </head> <body> @@ -15,9 +17,19 @@ <span>${name}</span> - <span>${artist}</span> <span>(${duration})</span> </div> - <div> - <button id="play-${trackId}" onclick="playTrack('${trackId}')">Play</button> - <button id="open-${trackId}" onclick="window.open('${trackHref}')">Open</button> + <div class="buttons"> + <span class="button" onclick="playTrack('${trackId}')"> + <span class="material-symbols-outlined"> + play_arrow + </span> + <span>Play</span> + </span> + <span class="button" onclick="window.open('${trackHref}')"> + <span class="material-symbols-outlined"> + link + </span> + <span>Open</span> + </span> </div> </div> </template> @@ -44,7 +56,25 @@ <button id="signin">Sign In to Spotify</button> </div> <div id="controls"> - <div id="greeting"></div> + <div> + <div> + <h3>Currently playing: + <span id="current-song"></span> + </h3> + </div> + <div> + <span class="button" onclick="pauseOrPlay()"> + <span class="material-symbols-outlined"> + play_pause + </span> + <span>Play/Pause</span> + </span> + </div> + </div> + + <div> + <h3>Search for a song by duration:</h3> + </div> <span id="minutes"> <input id="minutes-input" type="number" min="0" max="59" value="5" /> <label for="minutes-input">min</label> @@ -55,7 +85,10 @@ </span> <button id="search">Search</button> - <div id="results"></div> + <div> + <h3>Found songs:</h3> + <div id="results"></div> + </div> <button id="signout">Sign Out</button> </div> diff --git a/public/main.js b/public/main.js index 8861d38..43d896d 100644 --- a/public/main.js +++ b/public/main.js @@ -27,6 +27,8 @@ if (showSignInButton) { } else { signInButton.setAttribute('style', 'display: none'); controlsDiv.setAttribute('style', 'display: block'); + setCurrentPlayingSong(); + setInterval(setCurrentPlayingSong, 5 * 1000); } const signOutButton = document.getElementById('signout'); @@ -41,7 +43,7 @@ searchButton.addEventListener('click', async () => { const tracks = await search(minutes, seconds); const resultsDiv = document.getElementById('results'); if (tracks.length == 0) { - resultsDiv.innerHTML = 'No tracks found for that duration.'; + resultsDiv.innerHTML = 'No songs found for that duration.'; } else { resultsDiv.innerHTML = ''; tracks.forEach(track => { @@ -61,6 +63,17 @@ searchButton.addEventListener('click', async () => { } }); +async function setCurrentPlayingSong() { + const currentSongSpan = document.getElementById('current-song'); + const currentTrack = await getPlayingTrack(); + console.debug(currentTrack); + if (currentTrack && currentTrack.is_playing) { + currentSongSpan.innerText = `${currentTrack.item.name} by ${currentTrack.item.artists[0].name}`; + } else { + currentSongSpan.innerText = 'Nothing playing'; + } +} + function formatDuration(ms) { // Convert milliseconds to total seconds let totalSeconds = Math.floor(ms / 1000); @@ -78,11 +91,13 @@ function formatDuration(ms) { async function search(minutes, seconds) { const albums = await getAlbums(); + console.debug(albums); const foundTracks = []; for (let i = 0; i < albums.items.length; i++) { const album = albums.items[i]; const tracks = await getTracks(album.album.id); + console.debug(tracks) for (let j = 0; j < tracks.items.length; j++) { const track = tracks.items[j]; @@ -98,4 +113,14 @@ async function search(minutes, seconds) { } } return foundTracks; +} + +async function pauseOrPlay() { + const currentTrack = await getPlayingTrack(); + if (currentTrack && currentTrack.is_playing) { + await pause(); + } else { + await play(); + } + setCurrentPlayingSong(); } \ No newline at end of file diff --git a/public/signin.js b/public/signin.js index c344fe2..c36d78f 100644 --- a/public/signin.js +++ b/public/signin.js @@ -1,5 +1,5 @@ const clientId = '6c4c04a3751b4e409ddad7d71aad9949'; -const scope = 'user-library-read user-modify-playback-state'; +const scope = 'user-library-read user-modify-playback-state user-read-playback-state'; const prematureRefreshSeconds = 60 * 5; // 5 minutes before expiration function getSpotifyRedirectURL() { diff --git a/public/style.css b/public/style.css index ba46adf..3f74433 100644 --- a/public/style.css +++ b/public/style.css @@ -65,4 +65,27 @@ div { font-size: 18px; /* increase the font size */ } +} + +.material-symbols-outlined { + font-variation-settings: + 'FILL' 0, + 'wght' 400, + 'GRAD' 0, + 'opsz' 24 +} + +.button { + display: flex; + align-items: center; + cursor: pointer; + justify-items: center; + padding-left: 1em; + + /* vertical-align: middle; */ +} + +.buttons { + display: flex; + justify-content: start; } \ No newline at end of file