Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ functions/
build/
# Local Netlify folder
.netlify
data/
.venv/
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"liveServer.settings.ignoreFiles":["data/*"],
}
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,26 +56,30 @@ SPOTIFY_CLIENT_ID=<YOUR SPOTIFY CLIENT ID>
SPOTIFY_CLIENT_SECRET=<YOUR SPOTIFY SECRET>
REDIRECT_URI_AUTH=http://127.0.0.1:9000/.netlify/functions/api/auth/callback
REDIRECT_URI_HOME=http://127.0.0.1:8080/playlists.html
METRICS_ENABLED=<true or false>
```
You may also need to edit the sixth line of `dist/playlist.js` if you change the port that the backend api runs off of.
You may also need to edit the sixth line of `dist/util.js` if you change the port that the backend api runs off of.

At the current moment our application requires a server that is distributing the `dist` directory of the project to be able to function. This is to emulate what is eventually going to be hosted on netlify. Our dependencies include the `http-server` node module that can be used for this however, if it does not work you can install it with the following command.
```
npm install http-server
```
Editors like Visual Studio Code also have extensions that can provide a live http-server. If you decide to use something like that instead make sure you update the URIs accordingly. We will attempt to make this a more streamlined thing for running locally as the project progresses.

If metrics are enabled then on completion of going through a playlist the following metrics will be written to a file in the `data` directory.
- Per song: The total amount of time spent on the song, direction swiped, and song information.
- Per playlist: The total amount of time it took to go through the playlist.

### Running Locally

Once dependencies are installed and the `.env` file iat the moment as well due to the URI for API calls being hardcoded at the moment. s created and populated you can run the application with either of the following commands.
```
npm start (will just run the backend express app, use if you plan to use your own http server to distribute the frontend)
npm run dev (will launch both a server to distribute the frontend and execute the backend)
```
At the current moment we do not have a landing page set up so to check out what we have for the application you can go to either of these urls. Please note port `8080` is the default port for `http-server` and port `9000` is the default port for the express backend so if you decide to change them make sure you update the urls accordingly.
The below URL will bring you to the landing page for the application. Please note port `8080` is the default port for `http-server` and port `9000` is the default port for the express backend so if you decide to change them make sure you update the urls accordingly.
```
http://127.0.0.1:8080/index.html (song swiping demo, mobile only)
http://127.0.0.1:9000/.netlify/functions/api/auth/login (will ask you to sign in then redirect you to the work in progres playlist selection screen)
http://127.0.0.1:8080/index.html
```

Our unit tests can be ran with the following command.
Expand Down
Empty file.
30 changes: 13 additions & 17 deletions dist/cards.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
<!DOCTYPE html>
<html>

<head>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>SongSwipe</title>
Expand All @@ -13,29 +12,27 @@
<!-- Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link
href="https://fonts.googleapis.com/css2?family=Anton&family=DM+Serif+Display:ital@0;1&family=Roboto:ital,wght@0,100..900;1,100..900&display=swap"
rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Anton&family=DM+Serif+Display:ital@0;1&family=Roboto:ital,wght@0,100..900;1,100..900&display=swap" rel="stylesheet">

<!-- Enable standalone mode (full-screen) -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="My Web App">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
</head>
<meta name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"/>
</head>


<body>
<div id="overlay">
<div class="mobile-wrapper">
<div class="overlay-content">
<div id="loading_header_container">
<div id="loading_playlist_title">
<span id="loading_playlist_title_variable" class="bold_title">Loading...</span>
</div>
<div class="overlay-content">
<div id="loading_header_container">
<div id="loading_playlist_title">
<span id="loading_playlist_title_variable" class="bold_title">Loading...</span>
</div>
<div id="loading_song_card" class="card song_card">
<img id="loading_album_art" class="album">
</div>
<div id="loading_song_card" class="card song_card">
<img id="loading_album_art" class="album">
<div class="song_info_container">
<div class="song_info">
<span id="loading_title" class="bold_title"></span><br>
Expand Down Expand Up @@ -95,6 +92,5 @@
</div>
</div>
</div>
</body>

</body>
</html>
53 changes: 48 additions & 5 deletions dist/cards.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
let total_time = undefined;
let song_time = undefined;

$(document).ready(async function () {
// MAKE BUTTON PRETTY
// alert("Welcome to the SongSwipe demo!\n\nShown here is the swiping interface loaded with a existing Spotify playlist.\n\nSwipe right on songs you like\nSwipe left on ones you don't!")
Expand All @@ -14,6 +17,15 @@ $(document).ready(async function () {
headers.set('Authorization', access_token);
headers.set('Access-Control-Allow-Origin', '*');

// Check if metrics are enabled and set a boolean to not do the stuff if that aren't enabled
let enabledUrl = new URL(`${API_URI}/metrics/enabled`);
const enabled_request = new Request(enabledUrl.toString(), {
method: 'GET',
});
const enabled_response = await fetch(enabled_request);
const metrics_enabled = (await enabled_response.json()).enabled;
song_metrics = [];

// Hide Overlay Button until all the songs are loaded
const closeButton = document.getElementById('close-overlay');
closeButton.classList.add('hidden');
Expand Down Expand Up @@ -383,9 +395,11 @@ $(document).ready(async function () {
const overlay = document.getElementById('overlay');

// Close overlay when button is clicked
closeButton.addEventListener('click', function () {
closeButton.addEventListener('click', async function () {
overlay.classList.add('hidden');
// Starts playing by default
total_time = getSecondsSinceEpoch();
song_time = getSecondsSinceEpoch();
// Starts playing by default
// Plays song at the start of the tracklist
songPlayer(track_index);
});
Expand Down Expand Up @@ -500,7 +514,7 @@ $(document).ready(async function () {
});

// While finger is moving or mouse is being dragged...
$("#app_container").on("touchmove mousemove", function (event) {
$("#app_container").on("touchmove mousemove", async function (event) {
if (tracking) {
swipe_details = computeSwipeDetails(event);

Expand All @@ -517,16 +531,35 @@ $(document).ready(async function () {
card.style.transform = `translateX(${translateX}px) rotate(${rotateDeg}deg)`;

// If the song has been completed_swipe enough to declare it left or right swipe
let swipe_time = undefined;
if (swipe_details.distance > DISTANCE_TO_SWIPE) {
let track_id = songs[track_index].track_id;
if (swipe_details.direction === -1) {
console.log(songs[track_index]);
save_state = saveTrack(save_state, 'left', track_id, track_index, songs);
save(playlist_id, save_state, user_id)
swipe_time = getSecondsSinceEpoch() - song_time;
song_metrics.push({
'track_id': track_id,
'song_name': songs[track_index].name,
'swipe_time': swipe_time,
'direction': 'left'
});
if (metrics_enabled) sendTrackTime(playlist_id, user_id, track_id, songs[track_index].name, songs[track_index].artists[0],
songs[track_index].album_name, swipe_time, 'left');
} else if (swipe_details.direction === 1) {
console.log(songs[track_index]);
save_state = saveTrack(save_state, 'right', track_id, track_index, songs);
save(playlist_id, save_state, user_id);
swipe_time = getSecondsSinceEpoch() - song_time;
song_metrics.push({
'track_id': track_id,
'song_name': songs[track_index].name,
'swipe_time': swipe_time,
'direction': 'right'
});
if (metrics_enabled) sendTrackTime(playlist_id, user_id, track_id, songs[track_index].name, songs[track_index].artists[0],
songs[track_index].album_name, swipe_time, 'right');
}
// Plays new song after swipe
track_index += 1;
Expand All @@ -537,6 +570,7 @@ $(document).ready(async function () {

tracking = false;
completed_swipe = true;
song_time = getSecondsSinceEpoch();

// Add transition for smooth animation
$("#song_card").css({
Expand All @@ -553,7 +587,7 @@ $(document).ready(async function () {
});

// Once front song is fully out of frame
$("#song_card").one('transitionend', function () {
$("#song_card").one('transitionend', async function () {
// Variables for readability
let $next = $("#next_song_card");
let $current = $("#song_card");
Expand All @@ -573,7 +607,7 @@ $(document).ready(async function () {
$last.addClass("next_song_card").removeClass("last_song_card");

// Wait for the transition to finish, then remove transition property
$last.one("transitionend", function () {
$last.one("transitionend", async function () {
// Remove all transition properties to start with clean slate for future swipes
$next.css("transition", "");
$current.css("transition", "");
Expand All @@ -595,6 +629,15 @@ $(document).ready(async function () {
params.set('user_id', user_id);
params.set('playlist_id', playlist_id);

if (metrics_enabled) {
// song_metrics.forEach(async (element) => {
// await sendTrackTime(playlist_id, user_id, element['track_id'], element['song_name'], element['swipe_time'], element['direction']);
// });

let completion_time = (getSecondsSinceEpoch() - total_time) / SEC_PER_MIN;
await sendElapsedTime(playlist_id, user_id, completion_time);
}

window.location.href = window.location.pathname.replace('cards', 'stagingarea') + `?${params.toString()}`;
}
updateSongCard(songIndex, "last_song_card");
Expand Down
11 changes: 11 additions & 0 deletions dist/playlists.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,17 @@ $(document).ready(async function () {

// Redirect to cards page
card.addEventListener('click', async () => {
// Send request ot create files for metric collection will not do anything if metrics aren't enabled
let metrics_params = new URLSearchParams();
metrics_params.set('playlist_id', playlist.id);
metrics_params.set('user_id', data_user.id);
metrics_params.set('username', data_user.display_name);
metrics_params.set('playlist_name', playlist.name);
const information_metric_request = new Request(`${API_URI}/metrics/information?${metrics_params.toString()}`, {
method: 'POST',
});
await fetch(information_metric_request);

let params = new URLSearchParams();
params.set('playlist_id', playlist.id);

Expand Down
35 changes: 35 additions & 0 deletions dist/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ const API_URI = function() {
return uri + '/.netlify/functions/api';
}();

const SEC_PER_MIN = 60;

const getSecondsSinceEpoch = () => Math.floor(Date.now() / 1000);

/*
Expand Down Expand Up @@ -134,4 +136,37 @@ function moveTrack(target, source, destination, trackId) {
alert(`Track with ID ${trackId} not found in ${array_source}`);
}
return target;
}

async function sendTrackTime(playlist_id, user_id, track_id, track_name, track_artists, track_album, swipe_time, direction) {
let params = new URLSearchParams();
params.set('playlist_id', playlist_id);
params.set('user_id', user_id);
params.set('song_id', track_id);
params.set('song_artists', track_artists.replaceAll(",", ""));
params.set('song_album', track_album.replaceAll(",", ""));
params.set('song_name', track_name.replaceAll(",", ""));
params.set('swipe_time', swipe_time);
params.set('direction', direction);

const request = new Request(`${API_URI}/metrics/decision?${params.toString()}`, {
method: 'POST',
})

const response = await fetch(request);
response.status;
}

async function sendElapsedTime(playlist_id, user_id, total_time) {
let params = new URLSearchParams();
params.set('playlist_id', playlist_id);
params.set('user_id', user_id);
params.set('total_time', total_time);

const request = new Request(`${API_URI}/metrics/elapsed?${params.toString()}`, {
method: 'POST',
})

const response = await fetch(request);
response.status;
}
7 changes: 6 additions & 1 deletion src/api.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import express, { Router } from 'express'
import serverless from 'serverless-http'
import cors from 'cors';
import { authCallback, authLogin, authRefresh, playlistBuild, playlistData, userData, userPlaylists, songPreview, songRemove, playlistCreate, songAdd } from './endpoints';
import { authCallback, authLogin, authRefresh, playlistBuild, playlistData, userData, userPlaylists, songPreview, songRemove, playlistCreate, songAdd, metricsDecision, metricsElapsed, metricsInformation, metricsEnabled } from './endpoints';

const app = express();
const router = Router();
Expand All @@ -25,6 +25,11 @@ router.post('/playlist/add', songAdd);
router.delete('/playlist/remove', songRemove);
// Song related endpoints
router.get('/song', songPreview);
// Testing Metrics endpoints
router.get('/metrics/enabled', metricsEnabled);
router.post('/metrics/information', metricsInformation);
router.post('/metrics/decision', metricsDecision);
router.post('/metrics/elapsed', metricsElapsed);

// Netlify API setup
app.use("/.netlify/functions/api", router);
Expand Down
Loading