mirror of
https://github.com/janishutz/MusicPlayer.git
synced 2026-09-10 14:25:24 +02:00
feat: prepare adding songs
This commit is contained in:
+4
-1
@@ -14,6 +14,9 @@
|
|||||||
"@fortawesome/fontawesome-free": "^7.3.1",
|
"@fortawesome/fontawesome-free": "^7.3.1",
|
||||||
"@fortawesome/fontawesome-svg-core": "^7.3.1",
|
"@fortawesome/fontawesome-svg-core": "^7.3.1",
|
||||||
"@fortawesome/free-regular-svg-icons": "^7.3.1",
|
"@fortawesome/free-regular-svg-icons": "^7.3.1",
|
||||||
|
"@globalhive/vuejs-tour": "^3.0.1",
|
||||||
|
"@kyvg/vue3-notification": "^3.4.2",
|
||||||
|
"nprogress": "^0.2.0",
|
||||||
"pinia": "^4.0.2",
|
"pinia": "^4.0.2",
|
||||||
"vue": "^3.5.40",
|
"vue": "^3.5.40",
|
||||||
"vue-router": "^5.2.0"
|
"vue-router": "^5.2.0"
|
||||||
@@ -27,7 +30,7 @@
|
|||||||
"@vue/tsconfig": "^0.9.1",
|
"@vue/tsconfig": "^0.9.1",
|
||||||
"eslint-plugin-vue": "^10.10.0",
|
"eslint-plugin-vue": "^10.10.0",
|
||||||
"globals": "^17.11.0",
|
"globals": "^17.11.0",
|
||||||
"musickitjs-v3-types": "^1.0.0",
|
"musickitjs-v3-types": "^1.1.1",
|
||||||
"npm-run-all2": "^9.0.2",
|
"npm-run-all2": "^9.0.2",
|
||||||
"sass-embedded": "^1.103.1",
|
"sass-embedded": "^1.103.1",
|
||||||
"typescript": "~6.0.0",
|
"typescript": "~6.0.0",
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import PopupElement from '@/components/PopupElement.vue';
|
||||||
|
import player from '@/ts/player';
|
||||||
|
import {
|
||||||
|
sources
|
||||||
|
} from '@/ts/player/state';
|
||||||
|
|
||||||
|
const showPopup = defineModel<boolean>( {
|
||||||
|
'required': true
|
||||||
|
} );
|
||||||
|
|
||||||
|
const addSong = ( source: string ) => {
|
||||||
|
player.addSongFromSource( source );
|
||||||
|
showPopup.value = false;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<PopupElement v-model="showPopup" show-close>
|
||||||
|
<h1>Add Song</h1>
|
||||||
|
<div class="song-sources">
|
||||||
|
<div v-for="(source, index) in sources" :key="index" @click="() => addSong( source.id )">
|
||||||
|
<i v-if="source.icon" :class="['fa-solid', 'fa-' + source.icon]"></i>
|
||||||
|
<p>{{ source.name }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PopupElement>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.song-sources {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
width: 50vw;
|
||||||
|
height: 35vh;
|
||||||
|
justify-content: center;
|
||||||
|
overflow-y: scroll;
|
||||||
|
overflow-x: hidden;
|
||||||
|
|
||||||
|
>div {
|
||||||
|
width: 32%;
|
||||||
|
margin: 0.5%;
|
||||||
|
height: 50%;
|
||||||
|
background-color: var(--accent-background);
|
||||||
|
border-radius: 20px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
>.fa-solid {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -55,7 +55,7 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
display: block;
|
display: block;
|
||||||
transition: transform 0.5s ease, opacity 0.5s ease;
|
transition: transform 0.25s ease, opacity 0.25s ease;
|
||||||
|
|
||||||
>.close-icon {
|
>.close-icon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -74,6 +74,7 @@
|
|||||||
.popup-main {
|
.popup-main {
|
||||||
transform: scale(1.25);
|
transform: scale(1.25);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
transition: transform 0.15s ease, opacity 0.15s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.popup-backdrop {
|
.popup-backdrop {
|
||||||
|
|||||||
@@ -1,20 +1,28 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import AddSong from './AddSong.vue';
|
||||||
import SortableList from './SortableList.vue';
|
import SortableList from './SortableList.vue';
|
||||||
import {
|
import {
|
||||||
beautifyTime
|
beautifyTime
|
||||||
} from '@/ts/util/time';
|
} from '@/ts/util/time';
|
||||||
import player from '@/ts/player';
|
import player from '@/ts/player';
|
||||||
|
import {
|
||||||
|
ref
|
||||||
|
} from 'vue';
|
||||||
|
|
||||||
const queue = player.queue;
|
const queue = player.queue;
|
||||||
const idx = player.queueIdx;
|
const idx = player.queueIdx;
|
||||||
const isPlaying = player.isPlaying;
|
const isPlaying = player.isPlaying;
|
||||||
|
const showAddSong = ref( false );
|
||||||
|
|
||||||
|
const addSong = () => {
|
||||||
|
showAddSong.value = true;
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<button>
|
<button @click="addSong">
|
||||||
<i class="fa-solid fa-plus"></i>
|
<i class="fa-solid fa-plus"></i>
|
||||||
Add
|
Add
|
||||||
</button>
|
</button>
|
||||||
@@ -32,6 +40,7 @@
|
|||||||
Transmit
|
Transmit
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<AddSong v-model="showAddSong" />
|
||||||
<div>
|
<div>
|
||||||
<SortableList v-slot="{ item: song, index }" v-model="queue">
|
<SortableList v-slot="{ item: song, index }" v-model="queue">
|
||||||
<div class="song-cover-wrapper">
|
<div class="song-cover-wrapper">
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import type {
|
||||||
|
Song
|
||||||
|
} from '@/ts/dtype/playlist';
|
||||||
|
|
||||||
|
export interface CloudImport {
|
||||||
|
// TODO: Consider if should make selectable and display album / playlist contents to select specific songs
|
||||||
|
'name': string;
|
||||||
|
'kind': 'list' | 'nested-list';
|
||||||
|
'search': ( term: string ) => Promise<Song[]>;
|
||||||
|
'addSelected': ( index: number ) => Promise<Song[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Need to add a way to access Apple Music API everywhere (maybe configure MusicKitJS globally and consume it in its plugin later?)
|
||||||
|
export interface FileImport {
|
||||||
|
'name': string;
|
||||||
|
'process': ( files: File[] ) => Promise<Song[]>;
|
||||||
|
}
|
||||||
+32
@@ -16,6 +16,38 @@ export interface PlayerSourcePlugin {
|
|||||||
*/
|
*/
|
||||||
'id': string;
|
'id': string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Font-Awesome icon name (without fa and needs to be of free icons
|
||||||
|
*/
|
||||||
|
'icon'?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describe how files are loaded here
|
||||||
|
*/
|
||||||
|
'loading': {
|
||||||
|
/**
|
||||||
|
* Set to true if the user needs to load files
|
||||||
|
*/
|
||||||
|
'requiresLocalFiles': true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The allowed MIME types for loading associated files
|
||||||
|
*/
|
||||||
|
'MIMETypes': string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function used to associate the file to the songs of the playlist
|
||||||
|
* @param files - The files that were picked by the user
|
||||||
|
* @returns A promise resolving to an array of song identifiers (corresponding to Song.identifier, for association)
|
||||||
|
*/
|
||||||
|
'association': ( files: File[] ) => Promise<string[]>;
|
||||||
|
} | {
|
||||||
|
/**
|
||||||
|
* Set to true if the user needs to load files
|
||||||
|
*/
|
||||||
|
'requiresLocalFiles': false;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implement any login flow here. Optional
|
* Implement any login flow here. Optional
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -22,6 +22,12 @@ export const useLocalPlayer: PlayerSourcePluginInitializer = async (): Promise<P
|
|||||||
'pause': player.pause,
|
'pause': player.pause,
|
||||||
'stop': () => player.src = '',
|
'stop': () => player.src = '',
|
||||||
// TODO: Implement
|
// TODO: Implement
|
||||||
'addSongsFromThisSource': async () => []
|
'addSongsFromThisSource': async () => [],
|
||||||
|
'loading': {
|
||||||
|
'requiresLocalFiles': true,
|
||||||
|
'MIMETypes': 'audio/mp3,audio/wav',
|
||||||
|
// TODO: Implement
|
||||||
|
'association': async () => []
|
||||||
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ import type {
|
|||||||
PlayerSourcePlugin,
|
PlayerSourcePlugin,
|
||||||
PlayerSourcePluginInitializer
|
PlayerSourcePluginInitializer
|
||||||
} from '../interface';
|
} from '../interface';
|
||||||
|
import {
|
||||||
|
addFromAppleMusic
|
||||||
|
} from './search';
|
||||||
import {
|
import {
|
||||||
musicKitPlayback
|
musicKitPlayback
|
||||||
} from './playback';
|
} from './playback';
|
||||||
@@ -49,7 +52,10 @@ export const useMusicKit: PlayerSourcePluginInitializer = ( storefront: string =
|
|||||||
'seekTo': controls.seekTo,
|
'seekTo': controls.seekTo,
|
||||||
'login': login,
|
'login': login,
|
||||||
// TODO: Implement
|
// TODO: Implement
|
||||||
'addSongsFromThisSource': async () => []
|
'addSongsFromThisSource': async () => await addFromAppleMusic(),
|
||||||
|
'loading': {
|
||||||
|
'requiresLocalFiles': false
|
||||||
|
}
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
throw new Error( 'ERR_AUTH' );
|
throw new Error( 'ERR_AUTH' );
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import type {
|
||||||
|
MusicKitInstance
|
||||||
|
} from 'musickitjs-v3-types/MusicKitInstance';
|
||||||
|
|
||||||
|
export const searchAlbums = async ( instance: MusicKitInstance, term: string ) => {
|
||||||
|
const addSelected = ( idx: number ) => {
|
||||||
|
// TODO: Load album's songs and return them
|
||||||
|
Promise.resolve();
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import type {
|
||||||
|
MusicKitInstance
|
||||||
|
} from 'musickitjs-v3-types/MusicKitInstance';
|
||||||
|
import type {
|
||||||
|
Song
|
||||||
|
} from '@/ts/dtype/playlist';
|
||||||
|
|
||||||
|
export const addFromAppleMusic = async ( instance: MusicKitInstance ): Promise<Song[]> => {
|
||||||
|
return [];
|
||||||
|
};
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import type {
|
||||||
|
MusicKitInstance
|
||||||
|
} from 'musickitjs-v3-types/MusicKitInstance';
|
||||||
|
|
||||||
|
export const searchPlaylists = async ( instance: MusicKitInstance, userPlaylists: boolean = true ) => {
|
||||||
|
const addSelected = ( idx: number ) => {
|
||||||
|
// TODO: Load the playlist content and return songs
|
||||||
|
Promise.resolve();
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import type {
|
||||||
|
MusicKitInstance
|
||||||
|
} from 'musickitjs-v3-types/MusicKitInstance';
|
||||||
|
|
||||||
|
export const seachSongs = async ( instance: MusicKitInstance ) => {
|
||||||
|
const search = async ( term: string ) => {
|
||||||
|
const params = {
|
||||||
|
'term': term,
|
||||||
|
'types': [ 'songs' ],
|
||||||
|
'l': 'en-us'
|
||||||
|
};
|
||||||
|
|
||||||
|
await instance.api.music( '/v1/catalog/{{storefrontId}}/search', params );
|
||||||
|
};
|
||||||
|
|
||||||
|
const addSelected = ( idx: number ) => {
|
||||||
|
// TODO: Need to only return the selected song
|
||||||
|
Promise.resolve();
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import QueueViewer from '@/components/QueueViewer.vue';
|
import QueueViewer from '@/components/QueueViewer.vue';
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
Reference in New Issue
Block a user