diff --git a/backend/src/app.py b/backend/src/app.py index 247ba5f..c11cf37 100644 --- a/backend/src/app.py +++ b/backend/src/app.py @@ -9,7 +9,7 @@ from .state import State from .connect import get_connection from .room import Room from .song import Song, init_db, get_song_by_title_artist, add_song_in_db, get_song_by_uuid -from .song_fetch import lastfm_query_search, yt_get_audio_url, yt_search_song +from .song_fetch import query_search, yt_get_audio_url, yt_search_song from .qrcode_gen import generate_qr @@ -182,7 +182,8 @@ def add_song(): if (query := request.args.get("query")) is None: return error("Missing query") - info = lastfm_query_search(query) + if (info := query_search(query)) is None: + return error("Search failed") if (song := get_song_by_title_artist(info.title, info.artist)) is None: ## song not found, downolad from YT diff --git a/backend/src/gps.py b/backend/src/gps.py new file mode 100644 index 0000000..546531b --- /dev/null +++ b/backend/src/gps.py @@ -0,0 +1,27 @@ +import math + + +class Coordinates: + latitude: int + longitude: int + + +def distance_between_coords(lhs: Coordinates, rhs: Coordinates) -> float: + R = 6371000 # Earth radius in meters + + def to_rad(deg): + return (deg * math.pi) / 180 + + d_lat = to_rad(rhs.latitude - lhs.latitude) + d_lon = to_rad(rhs.longitude - lhs.longitude) + + a = (d_lat / 2) ** 2 + (to_rad(lhs.latitude) * to_rad(rhs.latitude)) * (d_lon / 2) ** 2 + + c = 2 * (a**0.5) / ((1 - a) ** 0.5) + + return R * c # distance in meters + + +def is_within_range(my_coords: Coordinates, target_coords: Coordinates, max_range: float) -> bool: + distance = distance_between_coords(my_coords, target_coords) + return distance <= max_range diff --git a/backend/src/song_fetch.py b/backend/src/song_fetch.py index ec15485..665e2c9 100644 --- a/backend/src/song_fetch.py +++ b/backend/src/song_fetch.py @@ -51,7 +51,23 @@ def _lastfm_getinfo(name: str, artist: str) -> tuple[str, list[str]]: # ( image ) -def lastfm_query_search(query: str) -> SongInfo: +def _yt_search(query: str) -> tuple[str, str]: + ydl_opts = { + "format": "bestaudio", + "default_search": "ytsearch1", + "outtmpl": "%(title)s.%(ext)s", + "skip_download": True, + } + + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + info = ydl.extract_info(query, download=False) + + first = info["entries"][0] + + return first["track"], first["artists"][0] + + +def query_search(query: str) -> SongInfo | None: name, artist = _lastfm_search(query) img_id, tags = _lastfm_getinfo(name, artist) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index ab99984..a620e77 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,6 +8,7 @@ "name": "frontend", "version": "0.0.1", "dependencies": { + "@lucide/svelte": "^0.536.0", "socket.io-client": "^4.8.1", "zod": "^4.0.14" }, @@ -30,7 +31,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, "license": "Apache-2.0", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -499,7 +499,6 @@ "version": "0.3.12", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -510,7 +509,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -520,20 +518,27 @@ "version": "1.5.4", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", - "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.29", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@lucide/svelte": { + "version": "0.536.0", + "resolved": "https://registry.npmjs.org/@lucide/svelte/-/svelte-0.536.0.tgz", + "integrity": "sha512-YAeoWU+0B/RriFZZ3wHno1FMkbrVrFdityuo2B0YuphD0vtJWXStzZkWLGVhT3jMb7zhugmhayIg+gI4+AZu1g==", + "license": "ISC", + "peerDependencies": { + "svelte": "^5" + } + }, "node_modules/@polka/url": { "version": "1.0.0-next.29", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", @@ -831,7 +836,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.5.tgz", "integrity": "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==", - "dev": true, "license": "MIT", "peerDependencies": { "acorn": "^8.9.0" @@ -1207,14 +1211,12 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, "license": "MIT" }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -1227,7 +1229,6 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", - "dev": true, "license": "Apache-2.0", "engines": { "node": ">= 0.4" @@ -1237,7 +1238,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", - "dev": true, "license": "Apache-2.0", "engines": { "node": ">= 0.4" @@ -1273,7 +1273,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -1433,14 +1432,12 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", - "dev": true, "license": "MIT" }, "node_modules/esrap": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.1.0.tgz", "integrity": "sha512-yzmPNpl7TBbMRC5Lj2JlJZNPml0tzqoqP5B1JXycNUwtqma9AKCO0M2wHrdgsHcy1WRW7S9rJknAMtByg3usgA==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" @@ -1487,7 +1484,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", - "dev": true, "license": "MIT", "dependencies": { "@types/estree": "^1.0.6" @@ -1756,14 +1752,12 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", - "dev": true, "license": "MIT" }, "node_modules/magic-string": { "version": "0.30.17", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" @@ -2181,7 +2175,6 @@ "version": "5.37.1", "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.37.1.tgz", "integrity": "sha512-h8arWpQZ+3z8eahyBT5KkiBOUsG6xvI5Ykg0ozRr9xEdImgSMUPUlOFWRNkUsT7Ti0DSUCTEbPoped0aoxFyWA==", - "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.3.0", @@ -2441,7 +2434,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz", "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==", - "dev": true, "license": "MIT" }, "node_modules/zod": { diff --git a/frontend/package.json b/frontend/package.json index d5aa485..f639403 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -28,7 +28,8 @@ "vite": "^7.0.4" }, "dependencies": { - "zod": "^4.0.14", - "socket.io-client": "^4.8.1" + "@lucide/svelte": "^0.536.0", + "socket.io-client": "^4.8.1", + "zod": "^4.0.14" } } diff --git a/frontend/src/lib/gps.ts b/frontend/src/lib/gps.ts index 7287833..f07e4c1 100644 --- a/frontend/src/lib/gps.ts +++ b/frontend/src/lib/gps.ts @@ -7,7 +7,7 @@ function geolocation_to_simple_coords(coordinates: GeolocationCoordinates): Coor return { latitude: coordinates.latitude, longitude: coordinates.longitude } } -export function get_coords(): Promise<{ coords: Coordinates | null, error: string | null }> { +export function get_coords(): Promise<{ coords: Coordinates | null; error: string | null }> { return new Promise((resolve) => { if (!navigator.geolocation) { resolve({ coords: null, error: "Geolocation is not supported by your browser" }) @@ -18,7 +18,7 @@ export function get_coords(): Promise<{ coords: Coordinates | null, error: strin console.log(gps_error) resolve({ coords: null, - error: `Unable to retrieve your location: (${gps_error.message})` + error: `Unable to retrieve your location: (${gps_error.message})`, }) return } @@ -26,7 +26,7 @@ export function get_coords(): Promise<{ coords: Coordinates | null, error: strin const success_callback = (gps_position: GeolocationPosition) => { resolve({ coords: geolocation_to_simple_coords(gps_position.coords), - error: null + error: null, }) return } @@ -34,29 +34,3 @@ export function get_coords(): Promise<{ coords: Coordinates | null, error: strin navigator.geolocation.getCurrentPosition(success_callback, error_callback) }) } - -function distance_between_coords( - lhs: Coordinates, - rhs: Coordinates -): number { - const R = 6371000; // earth radius in meters - const to_rad = (deg: number) => (deg * Math.PI) / 180; - - const d_lat = to_rad(rhs.latitude - lhs.latitude); - const d_lon = to_rad(rhs.longitude - lhs.longitude); - - const a = - Math.sin(d_lat / 2) ** 2 + - Math.cos(to_rad(lhs.latitude)) * - Math.cos(to_rad(rhs.latitude)) * - Math.sin(d_lon / 2) ** 2; - - const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); - - return R * c; // distance in meters -} - -export function is_within_range(my_coords: Coordinates, target_coords: Coordinates, max_range: number): boolean { - const distance = distance_between_coords(my_coords, target_coords) - return distance <= max_range -} diff --git a/frontend/src/lib/util.ts b/frontend/src/lib/util.ts deleted file mode 100644 index df0d44e..0000000 --- a/frontend/src/lib/util.ts +++ /dev/null @@ -1 +0,0 @@ -export type FetchError = { code: number, message: string } diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts index 203d6f8..1b806c5 100644 --- a/frontend/src/lib/utils.ts +++ b/frontend/src/lib/utils.ts @@ -65,3 +65,11 @@ export const triggerPlayNext = async function (roomId: string): Promise<[FetchEr }) return [null, songs, json["index"]] } + +export const getStreamingUrl = async function (uuid: string) { + let resp = await fetch("/api/song/audio?song=" + uuid) + + let json = await resp.json() + + return json["url"] +} diff --git a/frontend/src/routes/admin/[id]/+page.svelte b/frontend/src/routes/admin/[id]/+page.svelte index 9bcb9b4..e017cc5 100644 --- a/frontend/src/routes/admin/[id]/+page.svelte +++ b/frontend/src/routes/admin/[id]/+page.svelte @@ -3,21 +3,37 @@ import { type Song } from "$lib/types" import { onMount } from "svelte" import type { FetchError } from "$lib/types" - import { getQueueSongs, triggerPlayNext } from "$lib/utils.js" + import { getQueueSongs, getStreamingUrl, triggerPlayNext } from "$lib/utils.js" import Error from "$lib/components/Error.svelte" let { data } = $props() let queueSongs = $state([]) - let playingIndex = $state() + let playingIndex = $state(0) let returnError = $state() + let currentPlaying = $derived(queueSongs[playingIndex]) + let audioController = $state() + + async function playOnEnd() { + let url = await getStreamingUrl(currentPlaying.uuid) + if (audioController) { + audioController.src = url + audioController.play() + } + } + onMount(async () => { let songs, index ;[returnError, songs, index] = await getQueueSongs(data.roomId) queueSongs = songs playingIndex = index + + if (audioController) { + audioController.src = await getStreamingUrl(currentPlaying.uuid) + audioController.play() + } }) async function playNext() { @@ -37,5 +53,6 @@
+
{/if} diff --git a/frontend/src/routes/zesty/gps/+page.svelte b/frontend/src/routes/zesty/gps/+page.svelte index d5b25c0..ab9eacd 100644 --- a/frontend/src/routes/zesty/gps/+page.svelte +++ b/frontend/src/routes/zesty/gps/+page.svelte @@ -1,6 +1,6 @@ diff --git a/frontend/src/routes/zesty/icons/+page.svelte b/frontend/src/routes/zesty/icons/+page.svelte new file mode 100644 index 0000000..7307ebe --- /dev/null +++ b/frontend/src/routes/zesty/icons/+page.svelte @@ -0,0 +1,7 @@ + + + + + diff --git a/frontend/static/smerdo_radar_bonus.gif b/frontend/static/smerdo_radar_bonus.gif new file mode 100644 index 0000000..972945e Binary files /dev/null and b/frontend/static/smerdo_radar_bonus.gif differ diff --git a/frontend/tailwind.config.ts b/frontend/tailwind.config.ts deleted file mode 100644 index 16951d0..0000000 --- a/frontend/tailwind.config.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { Config } from "tailwindcss" -const { iconsPlugin, getIconCollections } = require("@egoist/tailwindcss-icons") - -export default { - content: ["./src/**/*.{html,js,svelte,ts}"], - - theme: { - extend: {}, - }, - - plugins: [ - iconsPlugin({ - // Select the icon collections you want to use - // You can also ignore this option to automatically discover all individual icon packages you have installed - // If you install @iconify/json, you should explicitly specify the collections you want to use, like this: - collections: getIconCollections(["lucide"]), - // If you want to use all icons from @iconify/json, you can do this: - // collections: getIconCollections("all"), - // and the more recommended way is to use `dynamicIconsPlugin`, see below. - }), - ], -} satisfies Config