mirror of
https://github.com/janishutz/MusicPlayerV2.git
synced 2025-11-25 04:54:23 +00:00
Fix custom playlist loading error
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,3 +4,4 @@ node_modules
|
||||
apple_private_key.p8
|
||||
musicplayerv2-server.zip
|
||||
dist
|
||||
package-lock.json
|
||||
|
||||
6816
MusicPlayerV2-GUI/package-lock.json
generated
6816
MusicPlayerV2-GUI/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -19,7 +19,7 @@
|
||||
"@rollup/plugin-inject": "^5.0.5",
|
||||
"buffer": "^6.0.3",
|
||||
"colorthief": "^2.6.0",
|
||||
"music-metadata-browser": "^2.5.10",
|
||||
"music-metadata": "^11.9.0",
|
||||
"musickit-typescript": "^1.2.4",
|
||||
"pinia": "^2.1.7",
|
||||
"socket.io-client": "^4.7.5",
|
||||
|
||||
@@ -1,24 +1,33 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Library</h1>
|
||||
<playlistsView :playlists="$props.playlists" @selected-playlist="( id ) => selectPlaylist( id )" :is-logged-in="$props.isLoggedIn"
|
||||
@custom-playlist="( pl ) => selectCustomPlaylist( pl )"></playlistsView>
|
||||
<playlistsView
|
||||
:playlists="$props.playlists"
|
||||
:is-logged-in="$props.isLoggedIn"
|
||||
@selected-playlist="( id ) => selectPlaylist( id )"
|
||||
@custom-playlist="( pl ) => selectCustomPlaylist( pl )"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import playlistsView from '@/components/playlistsView.vue';
|
||||
import type { ReadFile } from '@/scripts/song';
|
||||
import type {
|
||||
ReadFile
|
||||
} from '@/scripts/song';
|
||||
|
||||
const emits = defineEmits( [ 'selected-playlist', 'custom-playlist' ] );
|
||||
const emits = defineEmits( [
|
||||
'selected-playlist',
|
||||
'custom-playlist'
|
||||
] );
|
||||
|
||||
const selectPlaylist = ( id: string ) => {
|
||||
emits( 'selected-playlist', id );
|
||||
}
|
||||
};
|
||||
|
||||
const selectCustomPlaylist = ( playlist: ReadFile[] ) => {
|
||||
emits( 'custom-playlist', playlist );
|
||||
}
|
||||
};
|
||||
|
||||
defineProps( {
|
||||
'playlists': {
|
||||
|
||||
@@ -3,14 +3,18 @@
|
||||
<div id="notifications">
|
||||
<div class="message-box" :class="[ location, size ]" :style="'z-index: ' + ( messageType === 'hide' ? '-1' : '1000' )">
|
||||
<div class="message-container" :class="messageType">
|
||||
<button @click="handleNotifications();" class="close-notification"><span class="material-symbols-outlined close-notification-icon">close</span></button>
|
||||
<span class="material-symbols-outlined types hide" v-if="messageType == 'hide'">question_mark</span>
|
||||
<span class="material-symbols-outlined types" v-else-if="messageType == 'ok'" style="background-color: green;">done</span>
|
||||
<span class="material-symbols-outlined types" v-else-if="messageType == 'error'" style="background-color: red;">close</span>
|
||||
<span class="material-symbols-outlined types progress-spinner" v-else-if="messageType == 'progress'" style="background-color: blue;">progress_activity</span>
|
||||
<span class="material-symbols-outlined types" v-else-if="messageType == 'info'" style="background-color: lightblue;">info</span>
|
||||
<span class="material-symbols-outlined types" v-else-if="messageType == 'warning'" style="background-color: orangered;">warning</span>
|
||||
<p class="message" @click="notificationAction()">{{ notifications[ currentDID ] ? notifications[ currentDID ].message : '' }}</p>
|
||||
<button class="close-notification" @click="handleNotifications();">
|
||||
<span class="material-symbols-outlined close-notification-icon">close</span>
|
||||
</button>
|
||||
<span v-if="messageType == 'hide'" class="material-symbols-outlined types hide">question_mark</span>
|
||||
<span v-else-if="messageType == 'ok'" class="material-symbols-outlined types" style="background-color: green;">done</span>
|
||||
<span v-else-if="messageType == 'error'" class="material-symbols-outlined types" style="background-color: red;">close</span>
|
||||
<span v-else-if="messageType == 'progress'" class="material-symbols-outlined types progress-spinner" style="background-color: blue;">progress_activity</span>
|
||||
<span v-else-if="messageType == 'info'" class="material-symbols-outlined types" style="background-color: lightblue;">info</span>
|
||||
<span v-else-if="messageType == 'warning'" class="material-symbols-outlined types" style="background-color: orangered;">warning</span>
|
||||
<p class="message" @click="notificationAction()">
|
||||
{{ notifications[ currentDID ] ? notifications[ currentDID ].message : '' }}
|
||||
</p>
|
||||
<div :class="'countdown countdown-' + messageType" :style="'width: ' + ( 100 - ( currentTime - notificationDisplayStartTime ) / ( notifications[ currentDID ] ? notifications[ currentDID ].showDuration : 1 ) / 10 ) + '%'"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -19,28 +23,30 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import router from '@/router';
|
||||
import { onUnmounted, ref, type Ref } from 'vue';
|
||||
import {
|
||||
type Ref, onUnmounted, ref
|
||||
} from 'vue';
|
||||
|
||||
defineProps( {
|
||||
location: {
|
||||
type: String,
|
||||
'location': {
|
||||
'type': String,
|
||||
'default': 'topleft',
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
'size': {
|
||||
'type': String,
|
||||
'default': 'default',
|
||||
}
|
||||
// Size options: small, default (default option), big, bigger, huge
|
||||
} );
|
||||
|
||||
interface Notification {
|
||||
message: string;
|
||||
showDuration: number;
|
||||
messageType: string;
|
||||
priority: string;
|
||||
id: number;
|
||||
redirect?: string;
|
||||
openInNewTab?: boolean;
|
||||
'message': string;
|
||||
'showDuration': number;
|
||||
'messageType': string;
|
||||
'priority': string;
|
||||
'id': number;
|
||||
'redirect'?: string;
|
||||
'openInNewTab'?: boolean;
|
||||
}
|
||||
|
||||
interface NotificationList {
|
||||
@@ -51,11 +57,17 @@
|
||||
const queue: Ref<number[]> = ref( [] );
|
||||
const currentDID: Ref<number> = ref( 0 );
|
||||
const messageType: Ref<string> = ref( 'hide' );
|
||||
const currentID = ref( { 'critical': 0, 'medium': 1000, 'low': 10000 } );
|
||||
const currentID = ref( {
|
||||
'critical': 0,
|
||||
'medium': 1000,
|
||||
'low': 10000
|
||||
} );
|
||||
const notificationDisplayStartTime: Ref<number> = ref( 0 );
|
||||
const currentTime: Ref<number> = ref( 0 );
|
||||
|
||||
let progressBar = 0;
|
||||
let notificationTimeout = 0;
|
||||
|
||||
const notificationAction = () => {
|
||||
if ( notifications.value[ currentDID.value ] ) {
|
||||
if ( notifications.value[ currentDID.value ].redirect ) {
|
||||
@@ -76,7 +88,9 @@
|
||||
* @param {string} priority The priority of the message: 'low', 'normal', 'critical'
|
||||
* @returns {number}
|
||||
*/
|
||||
const createNotification = ( message: string, showDuration: number, msgType: string, priority: string, redirect?: string, openInNewTab?: boolean ): number => {
|
||||
const createNotification = (
|
||||
message: string, showDuration: number, msgType: string, priority: string, redirect?: string, openInNewTab?: boolean
|
||||
): number => {
|
||||
/*
|
||||
Takes a notification options array that contains: message, showDuration (in seconds), msgType (ok, error, progress, info) and priority (low, normal, critical).
|
||||
Returns a notification ID which can be used to cancel the notification. The component will throttle notifications and display
|
||||
@@ -94,14 +108,25 @@
|
||||
currentID.value[ 'low' ] += 1;
|
||||
id = currentID.value[ 'low' ];
|
||||
}
|
||||
notifications.value[ id ] = { 'message': message, 'showDuration': showDuration, 'messageType': msgType, 'priority': priority, 'id': id, redirect: redirect, openInNewTab: openInNewTab };
|
||||
|
||||
notifications.value[ id ] = {
|
||||
'message': message,
|
||||
'showDuration': showDuration,
|
||||
'messageType': msgType,
|
||||
'priority': priority,
|
||||
'id': id,
|
||||
'redirect': redirect,
|
||||
'openInNewTab': openInNewTab
|
||||
};
|
||||
queue.value.push( id );
|
||||
console.log( 'scheduled notification: ' + id + ' (' + message + ')' );
|
||||
|
||||
if ( ( new Date().getTime() - notificationDisplayStartTime.value ) / 1000 >= ( notifications.value[ currentDID.value ] ? notifications.value[ currentDID.value ].showDuration : 0 ) || messageType.value === 'hide' ) {
|
||||
handleNotifications();
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update a notification's message after creating it
|
||||
@@ -113,7 +138,7 @@
|
||||
if ( notifications.value[ id ] ) {
|
||||
notifications.value[ id ].message = message;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
@@ -127,26 +152,31 @@
|
||||
} catch ( error ) {
|
||||
console.log( 'notification to be deleted is nonexistent or currently being displayed' );
|
||||
}
|
||||
|
||||
try {
|
||||
queue.value.splice( queue.value.indexOf( id ), 1 );
|
||||
} catch {
|
||||
console.debug( 'queue empty' );
|
||||
}
|
||||
|
||||
if ( currentDID.value == id ) {
|
||||
try {
|
||||
clearTimeout( notificationTimeout );
|
||||
} catch (err) { /* empty */ }
|
||||
} catch ( err ) { /* empty */ }
|
||||
|
||||
handleNotifications();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleNotifications = () => {
|
||||
notificationDisplayStartTime.value = new Date().getTime();
|
||||
queue.value.sort();
|
||||
|
||||
if ( queue.value.length > 0 ) {
|
||||
if ( currentDID.value !== 0 ) {
|
||||
delete notifications.value[ currentDID.value ];
|
||||
}
|
||||
|
||||
currentDID.value = notifications.value[ queue.value[ 0 ] ][ 'id' ];
|
||||
messageType.value = notifications.value[ queue.value[ 0 ] ].messageType;
|
||||
queue.value.reverse();
|
||||
@@ -158,22 +188,24 @@
|
||||
} else {
|
||||
try {
|
||||
clearInterval( progressBar );
|
||||
} catch (err) { /* empty */ }
|
||||
} catch ( err ) { /* empty */ }
|
||||
|
||||
messageType.value = 'hide';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const progressBarHandler = () => {
|
||||
currentTime.value = new Date().getTime();
|
||||
}
|
||||
};
|
||||
|
||||
onUnmounted( () => {
|
||||
try {
|
||||
clearInterval( progressBar );
|
||||
} catch (err) { /* empty */ }
|
||||
} catch ( err ) { /* empty */ }
|
||||
|
||||
try {
|
||||
clearInterval( notificationTimeout );
|
||||
} catch (err) { /* empty */ }
|
||||
} catch ( err ) { /* empty */ }
|
||||
} );
|
||||
|
||||
defineExpose( {
|
||||
|
||||
@@ -4,51 +4,109 @@
|
||||
<h3>WARNING!</h3>
|
||||
<p>A client display is being tampered with!</p>
|
||||
<p>A desktop notification with a warning has already been dispatched.</p>
|
||||
<button @click="dismissNotification()" class="simple-button">Ok</button>
|
||||
<button class="simple-button" @click="dismissNotification()">
|
||||
Ok
|
||||
</button>
|
||||
|
||||
<div class="flash"></div>
|
||||
</div>
|
||||
<div class="player">
|
||||
<div :class="'main-player' + ( isShowingFullScreenPlayer ? ' full-screen' : '' )">
|
||||
<div :class="'song-name-wrapper' + ( isShowingFullScreenPlayer ? ' full-screen' : '' )" @click="controlUI( 'show' )">
|
||||
<img src="https://github.com/simplePCBuilding/MusicPlayerV2/raw/master/assets/logo.png" alt="MusicPlayer Logo" class="logo-player" v-if="coverArt === ''">
|
||||
<img :src="coverArt" alt="MusicPlayer Logo" class="logo-player" v-else>
|
||||
<img
|
||||
v-if="coverArt === ''"
|
||||
src="https://github.com/simplePCBuilding/MusicPlayerV2/raw/master/assets/logo.png"
|
||||
alt="MusicPlayer Logo"
|
||||
class="logo-player"
|
||||
>
|
||||
<img
|
||||
v-else
|
||||
:src="coverArt"
|
||||
alt="MusicPlayer Logo"
|
||||
class="logo-player"
|
||||
>
|
||||
<div class="name-time">
|
||||
<p class="song-name">{{ currentlyPlayingSongName }} <i v-if="currentlyPlayingSongArtist">by {{ currentlyPlayingSongArtist }}</i></p>
|
||||
<div class="playback" v-if="!isShowingFullScreenPlayer">
|
||||
<p class="song-name">
|
||||
{{ currentlyPlayingSongName }} <i v-if="currentlyPlayingSongArtist">by {{ currentlyPlayingSongArtist }}</i>
|
||||
</p>
|
||||
<div v-if="!isShowingFullScreenPlayer" class="playback">
|
||||
<div class="playback-pos-wrapper">
|
||||
<p class="playback-pos">{{ nicePlaybackPos }}</p>
|
||||
<p class="playback-pos">
|
||||
{{ nicePlaybackPos }}
|
||||
</p>
|
||||
<p> / </p>
|
||||
<p class="playback-duration">{{ niceDuration }}</p>
|
||||
<p class="playback-duration">
|
||||
{{ niceDuration }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="'controls-wrapper' + ( isShowingFullScreenPlayer ? ' full-screen' : '' )" :style="playlist.length > 0 ? '' : 'pointer-events: none'">
|
||||
<div class="main-controls">
|
||||
<span class="material-symbols-outlined controls next-previous" @click="control( 'previous' )" id="previous" v-if="isShowingFullScreenPlayer">skip_previous</span>
|
||||
<span class="material-symbols-outlined controls forward-back" @click="control( 'back' )" :style="'rotate: -' + 360 * clickCountBack + 'deg;'" v-if="isShowingFullScreenPlayer">replay_10</span>
|
||||
<span class="material-symbols-outlined controls" v-if="isPlaying" @click="playPause()" id="play-pause">pause</span>
|
||||
<span class="material-symbols-outlined controls" v-else @click="playPause()" id="play-pause">play_arrow</span>
|
||||
<span class="material-symbols-outlined controls forward-back" @click="control( 'forward' )" :style="'rotate: ' + 360 * clickCountForward + 'deg;'" v-if="isShowingFullScreenPlayer">forward_10</span>
|
||||
<span class="material-symbols-outlined controls next-previous" @click="control( 'next' )" id="next">skip_next</span>
|
||||
<span
|
||||
v-if="isShowingFullScreenPlayer"
|
||||
id="previous"
|
||||
class="material-symbols-outlined controls next-previous"
|
||||
@click="control( 'previous' )"
|
||||
>skip_previous</span>
|
||||
<span
|
||||
v-if="isShowingFullScreenPlayer"
|
||||
class="material-symbols-outlined controls forward-back"
|
||||
:style="'rotate: -' + 360 * clickCountBack + 'deg;'"
|
||||
@click="control( 'back' )"
|
||||
>replay_10</span>
|
||||
<span
|
||||
v-if="isPlaying"
|
||||
id="play-pause"
|
||||
class="material-symbols-outlined controls"
|
||||
@click="playPause()"
|
||||
>pause</span>
|
||||
<span
|
||||
v-else
|
||||
id="play-pause"
|
||||
class="material-symbols-outlined controls"
|
||||
@click="playPause()"
|
||||
>play_arrow</span>
|
||||
<span
|
||||
v-if="isShowingFullScreenPlayer"
|
||||
class="material-symbols-outlined controls forward-back"
|
||||
:style="'rotate: ' + 360 * clickCountForward + 'deg;'"
|
||||
@click="control( 'forward' )"
|
||||
>forward_10</span>
|
||||
<span id="next" class="material-symbols-outlined controls next-previous" @click="control( 'next' )">skip_next</span>
|
||||
</div>
|
||||
|
||||
<div class="slider-wrapper" v-if="isShowingFullScreenPlayer">
|
||||
<div v-if="isShowingFullScreenPlayer" class="slider-wrapper">
|
||||
<div class="slider-pb-pos">
|
||||
<p class="playback-pos">{{ nicePlaybackPos }}</p>
|
||||
<p class="playback-duration" @click="toggleRemaining()" title="Toggle between remaining time and song duration">{{ niceDuration }}</p>
|
||||
<p class="playback-pos">
|
||||
{{ nicePlaybackPos }}
|
||||
</p>
|
||||
<p class="playback-duration" title="Toggle between remaining time and song duration" @click="toggleRemaining()">
|
||||
{{ niceDuration }}
|
||||
</p>
|
||||
</div>
|
||||
<sliderView :position="pos" :active="true" :duration="duration" name="main" @pos="( pos ) => goToPos( pos )"></sliderView>
|
||||
<sliderView
|
||||
:position="pos"
|
||||
:active="true"
|
||||
:duration="duration"
|
||||
name="main"
|
||||
@pos="( pos ) => goToPos( pos )"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="shuffle-repeat" v-if="isShowingFullScreenPlayer">
|
||||
<span class="material-symbols-outlined controls" @click="control( 'repeat' )" style="margin-right: auto;">repeat{{ repeatMode }}</span>
|
||||
<div v-if="isShowingFullScreenPlayer" class="shuffle-repeat">
|
||||
<span class="material-symbols-outlined controls" style="margin-right: auto;" @click="control( 'repeat' )">repeat{{ repeatMode }}</span>
|
||||
<div style="margin-right: auto; pointer-events: all;">
|
||||
<span class="material-symbols-outlined controls" @click="control( 'start-share' )" title="Share your playlist on a public playlist page (opens a configuration window)" v-if="!isConnectedToNotifier">share</span>
|
||||
<span
|
||||
v-if="!isConnectedToNotifier"
|
||||
class="material-symbols-outlined controls"
|
||||
title="Share your playlist on a public playlist page (opens a configuration window)"
|
||||
@click="control( 'start-share' )"
|
||||
>share</span>
|
||||
<div v-else>
|
||||
<span class="material-symbols-outlined controls" @click="control( 'stop-share' )" title="Stop sharing your playlist on a public playlist page">close</span>
|
||||
<span class="material-symbols-outlined controls" @click="control( 'show-share' )" title="Show information on the share, including URL to connect to">info</span>
|
||||
<span class="material-symbols-outlined controls" title="Stop sharing your playlist on a public playlist page" @click="control( 'stop-share' )">close</span>
|
||||
<span class="material-symbols-outlined controls" title="Show information on the share, including URL to connect to" @click="control( 'show-share' )">info</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="material-symbols-outlined controls" @click="control( 'shuffle' )">shuffle{{ shuffleMode }}</span>
|
||||
@@ -58,33 +116,49 @@
|
||||
</div>
|
||||
<div :class="'playlist-view' + ( isShowingFullScreenPlayer ? '' : ' hidden' )">
|
||||
<span class="material-symbols-outlined close-fullscreen" @click="controlUI( 'hide' )">close</span>
|
||||
<playlistView :playlist="playlist" class="pl-wrapper" :currently-playing="currentlyPlayingSongIndex" :is-playing="isPlaying" :pos="pos"
|
||||
@control="( action ) => { control( action ) }" @play-song="( song ) => { playSong( song ) }"
|
||||
@add-new-songs="( songs ) => addNewSongs( songs )" @playlist-reorder="( move ) => moveSong( move )"
|
||||
<playlistView
|
||||
:playlist="playlist"
|
||||
class="pl-wrapper"
|
||||
:currently-playing="currentlyPlayingSongIndex"
|
||||
:is-playing="isPlaying"
|
||||
:pos="pos"
|
||||
:is-logged-into-apple-music="player.isLoggedIn"
|
||||
@control="( action ) => { control( action ) }"
|
||||
@play-song="( song ) => { playSong( song ) }"
|
||||
@add-new-songs="( songs ) => addNewSongs( songs )"
|
||||
@playlist-reorder="( move ) => moveSong( move )"
|
||||
@add-new-songs-apple-music="( song ) => addNewSongFromObject( song )"
|
||||
@delete-song="song => removeSongFromPlaylist( song )"
|
||||
@clear-playlist="() => clearPlaylist()"
|
||||
@send-additional-info="() => sendAdditionalInfo()"></playlistView>
|
||||
@send-additional-info="() => sendAdditionalInfo()"
|
||||
/>
|
||||
</div>
|
||||
<notificationsModule ref="notifications" location="bottomleft" size="bigger"></notificationsModule>
|
||||
<popupModule @update="( data ) => popupReturnHandler( data )" ref="popup"></popupModule>
|
||||
<audio src="" id="local-audio" controls="false"></audio>
|
||||
<notificationsModule ref="notifications" location="bottomleft" size="bigger" />
|
||||
<popupModule ref="popup" @update="( data ) => popupReturnHandler( data )" />
|
||||
<audio id="local-audio" src="" controls="false"></audio>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, type Ref } from 'vue';
|
||||
import playlistView from '@/components/playlistView.vue';
|
||||
import type {
|
||||
ReadFile, Song, SongMove
|
||||
} from '@/scripts/song';
|
||||
import {
|
||||
type Ref, ref
|
||||
} from 'vue';
|
||||
import MusicKitJSWrapper from '@/scripts/music-player';
|
||||
import sliderView from './sliderView.vue';
|
||||
import type { ReadFile, Song, SongMove } from '@/scripts/song';
|
||||
import { parseBlob } from 'music-metadata-browser';
|
||||
import notificationsModule from './notificationsModule.vue';
|
||||
import { useUserStore } from '@/stores/userStore';
|
||||
import NotificationHandler from '@/scripts/notificationHandler';
|
||||
import notificationsModule from './notificationsModule.vue';
|
||||
import {
|
||||
parseBlob
|
||||
} from 'music-metadata';
|
||||
import playlistView from '@/components/playlistView.vue';
|
||||
import popupModule from './popupModule.vue';
|
||||
import sliderView from './sliderView.vue';
|
||||
import {
|
||||
useUserStore
|
||||
} from '@/stores/userStore';
|
||||
|
||||
const isPlaying = ref( false );
|
||||
const repeatMode = ref( '' );
|
||||
@@ -100,7 +174,9 @@
|
||||
const nicePlaybackPos = ref( '00:00' );
|
||||
const niceDuration = ref( '00:00' );
|
||||
const isShowingRemainingTime = ref( false );
|
||||
|
||||
let isShowingRemainingTimeBackend = false;
|
||||
|
||||
const currentlyPlayingSongArtist = ref( '' );
|
||||
const pos = ref( 0 );
|
||||
const duration = ref( 0 );
|
||||
@@ -110,6 +186,7 @@
|
||||
const popup = ref( popupModule );
|
||||
const roomName = ref( '' );
|
||||
const isShowingWarning = ref( false );
|
||||
|
||||
let currentlyOpenPopup = '';
|
||||
let logoutErrorNotification = -1;
|
||||
|
||||
@@ -118,19 +195,24 @@
|
||||
document.addEventListener( 'musicplayer:autherror', () => {
|
||||
localStorage.setItem( 'close-tab', 'true' );
|
||||
isConnectedToNotifier.value = false;
|
||||
logoutErrorNotification = notifications.value.createNotification( 'You appear to have been logged out. Click to log in again!', 600, 'error', 'critical', '/', true );
|
||||
logoutErrorNotification = notifications.value.createNotification(
|
||||
'You appear to have been logged out. Click to log in again!', 600, 'error', 'critical', '/', true
|
||||
);
|
||||
} );
|
||||
|
||||
window.addEventListener( 'storage', () => {
|
||||
if ( localStorage.getItem( 'login-ok' ) === 'true' ) {
|
||||
notifications.value.cancelNotification( logoutErrorNotification );
|
||||
notifications.value.createNotification( 'Logged in again. You will have to reconnect to the share!', 20, 'ok', 'normal' );
|
||||
notifications.value.createNotification(
|
||||
'Logged in again. You will have to reconnect to the share!', 20, 'ok', 'normal'
|
||||
);
|
||||
localStorage.removeItem( 'login-ok' );
|
||||
}
|
||||
} );
|
||||
|
||||
const playPause = () => {
|
||||
isPlaying.value = !isPlaying.value;
|
||||
|
||||
if ( isPlaying.value ) {
|
||||
player.control( 'play' );
|
||||
startProgressTracker();
|
||||
@@ -138,17 +220,17 @@
|
||||
player.control( 'pause' );
|
||||
stopProgressTracker();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const goToPos = ( position: number ) => {
|
||||
player.goToPos( position );
|
||||
pos.value = position;
|
||||
notificationHandler.emit( 'playback-start-update', new Date().getTime() - pos.value * 1000 );
|
||||
}
|
||||
};
|
||||
|
||||
const toggleRemaining = () => {
|
||||
isShowingRemainingTime.value = !isShowingRemainingTime.value;
|
||||
}
|
||||
};
|
||||
|
||||
const control = ( action: string ) => {
|
||||
if ( action === 'pause' ) {
|
||||
@@ -184,11 +266,13 @@
|
||||
getDetails();
|
||||
notificationHandler.emit( 'playlist-update', playlist.value );
|
||||
}
|
||||
|
||||
notificationHandler.emit( 'playlist-index-update', player.getQueueID() );
|
||||
getDetails();
|
||||
} else if ( action === 'forward' ) {
|
||||
clickCountForward.value += 1;
|
||||
if( player.control( 'skip-10' ) ) {
|
||||
|
||||
if ( player.control( 'skip-10' ) ) {
|
||||
startProgressTracker();
|
||||
} else {
|
||||
pos.value = player.getPlaybackPos();
|
||||
@@ -196,7 +280,8 @@
|
||||
}
|
||||
} else if ( action === 'back' ) {
|
||||
clickCountBack.value += 1;
|
||||
if( player.control( 'back-10' ) ) {
|
||||
|
||||
if ( player.control( 'back-10' ) ) {
|
||||
startProgressTracker();
|
||||
} else {
|
||||
pos.value = player.getPlaybackPos();
|
||||
@@ -218,19 +303,19 @@
|
||||
startProgressTracker();
|
||||
} else if ( action === 'start-share' ) {
|
||||
popup.value.openPopup( {
|
||||
title: 'Define a share name',
|
||||
popupType: 'input',
|
||||
subtitle: 'A share allows others to join your playlist and see the current song, the playback position and the upcoming songs. You can get the link to the page, once the share is set up. Please choose a name, which will then be part of the URL with which others can join the share. The anti tamper feature notifies you, whenever a user leaves the fancy view.',
|
||||
data: [
|
||||
'title': 'Define a share name',
|
||||
'popupType': 'input',
|
||||
'subtitle': 'A share allows others to join your playlist and see the current song, the playback position and the upcoming songs. You can get the link to the page, once the share is set up. Please choose a name, which will then be part of the URL with which others can join the share. The anti tamper feature notifies you, whenever a user leaves the fancy view.',
|
||||
'data': [
|
||||
{
|
||||
name: 'Share Name',
|
||||
dataType: 'text',
|
||||
id: 'roomName'
|
||||
'name': 'Share Name',
|
||||
'dataType': 'text',
|
||||
'id': 'roomName'
|
||||
},
|
||||
{
|
||||
name: 'Use Anti-Tamper?',
|
||||
dataType: 'checkbox',
|
||||
id: 'useAntiTamper'
|
||||
'name': 'Use Anti-Tamper?',
|
||||
'dataType': 'checkbox',
|
||||
'id': 'useAntiTamper'
|
||||
}
|
||||
]
|
||||
} );
|
||||
@@ -239,19 +324,21 @@
|
||||
if ( confirm( 'Do you really want to stop sharing?' ) ) {
|
||||
notificationHandler.disconnect();
|
||||
isConnectedToNotifier.value = false;
|
||||
notifications.value.createNotification( 'Disconnected successfully!', 5, 'ok', 'normal' );
|
||||
notifications.value.createNotification(
|
||||
'Disconnected successfully!', 5, 'ok', 'normal'
|
||||
);
|
||||
}
|
||||
} else if ( action === 'show-share' ) {
|
||||
popup.value.openPopup( {
|
||||
title: 'Details on share',
|
||||
subtitle: 'You are currently connected to share "' + roomName.value
|
||||
+ '". \nYou can connect to it via <a href="https://music.janishutz.com/share/' + roomName.value + '" target="_blank">https://music.janishutz.com/share/' + roomName.value + '</a>'
|
||||
+ '. \n\nYou can connect to the fancy showcase screen using this link: <a href="https://music.janishutz.com/fancy/' + roomName.value + '" target="_blank">https://music.janishutz.com/fancy/' + roomName.value + '</a>'
|
||||
+ '. Be aware that this one will use significantly more system AND network resources, so only use that for a screen that is front and center, not for a QR code to have all people connect to.'
|
||||
'title': 'Details on share',
|
||||
'subtitle': 'You are currently connected to share "' + roomName.value
|
||||
+ '". \nYou can connect to it via <a href="https://music.janishutz.com/share/' + roomName.value + '" target="_blank">https://music.janishutz.com/share/' + roomName.value + '</a>'
|
||||
+ '. \n\nYou can connect to the fancy showcase screen using this link: <a href="https://music.janishutz.com/fancy/' + roomName.value + '" target="_blank">https://music.janishutz.com/fancy/' + roomName.value + '</a>'
|
||||
+ '. Be aware that this one will use significantly more system AND network resources, so only use that for a screen that is front and center, not for a QR code to have all people connect to.'
|
||||
} );
|
||||
currentlyOpenPopup = 'share-details';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const controlUI = ( action: string ) => {
|
||||
@@ -263,28 +350,30 @@
|
||||
isShowingFullScreenPlayer.value = false;
|
||||
isShowingRemainingTimeBackend = isShowingRemainingTime.value;
|
||||
isShowingRemainingTime.value = false;
|
||||
|
||||
try {
|
||||
prepNiceDurationTime( player.getPlayingSong() );
|
||||
} catch ( err ) { /* empty */ }
|
||||
|
||||
emits( 'playerStateChange', 'hide' );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getPlaylists = ( cb: ( data: object ) => void ) => {
|
||||
player.getUserPlaylists( cb );
|
||||
}
|
||||
};
|
||||
|
||||
const logIntoAppleMusic = () => {
|
||||
player.logIn();
|
||||
}
|
||||
};
|
||||
|
||||
const getAuth = (): boolean[] => {
|
||||
return player.getAuth();
|
||||
}
|
||||
};
|
||||
|
||||
const skipLogin = () => {
|
||||
player.init();
|
||||
}
|
||||
};
|
||||
|
||||
const selectPlaylist = ( id: string ) => {
|
||||
currentlyPlayingSongArtist.value = '';
|
||||
@@ -298,20 +387,26 @@
|
||||
notificationHandler.emit( 'playlist-update', playlist.value );
|
||||
}, 2000 );
|
||||
} );
|
||||
}
|
||||
};
|
||||
|
||||
const selectCustomPlaylist = async ( pl: ReadFile[] ) => {
|
||||
let n = notifications.value.createNotification( 'Analyzing playlist', 200, 'progress', 'normal' );
|
||||
let n = notifications.value.createNotification(
|
||||
'Analyzing playlist', 200, 'progress', 'normal'
|
||||
);
|
||||
|
||||
playlist.value = [];
|
||||
let plLoad: Song[] = [];
|
||||
const plLoad: Song[] = [];
|
||||
|
||||
for ( let element in pl ) {
|
||||
try {
|
||||
plLoad.push( await fetchSongData( pl[ element ] ) );
|
||||
} catch ( e ) {
|
||||
console.error( e );
|
||||
}
|
||||
notifications.value.updateNotification( n, `Analyzing playlist (${element}/${pl.length})` );
|
||||
|
||||
notifications.value.updateNotification( n, `Analyzing playlist (${ element }/${ pl.length })` );
|
||||
}
|
||||
|
||||
playlist.value = plLoad;
|
||||
player.setPlaylist( playlist.value );
|
||||
player.prepare( 0 );
|
||||
@@ -322,82 +417,106 @@
|
||||
notificationHandler.emit( 'playlist-update', playlist.value );
|
||||
}, 2000 );
|
||||
notifications.value.cancelNotification( n );
|
||||
notifications.value.createNotification( 'Playlist loaded', 10, 'ok', 'normal' );
|
||||
}
|
||||
notifications.value.createNotification(
|
||||
'Playlist loaded', 10, 'ok', 'normal'
|
||||
);
|
||||
};
|
||||
|
||||
const fetchSongData = ( songDetails: ReadFile ): Promise<Song> => {
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
fetch( songDetails.url ).then( res => {
|
||||
if ( res.status === 200 ) {
|
||||
res.blob().then( blob => {
|
||||
parseBlob( blob ).then( data => {
|
||||
try {
|
||||
player.findSongOnAppleMusic( data.common.title ?? songDetails.filename.split( '.' )[ 0 ] ).then( d => {
|
||||
let url = d.data.results.songs.data[ 0 ].attributes.artwork.url;
|
||||
url = url.replace( '{w}', String( d.data.results.songs.data[ 0 ].attributes.artwork.width ) );
|
||||
url = url.replace( '{h}', String( d.data.results.songs.data[ 0 ].attributes.artwork.height ) );
|
||||
const song: Song = {
|
||||
artist: d.data.results.songs.data[ 0 ].attributes.artistName,
|
||||
title: d.data.results.songs.data[ 0 ].attributes.name,
|
||||
duration: data.format.duration ?? ( d.data.results.songs.data[ 0 ].attributes.durationInMillis / 1000 ),
|
||||
id: songDetails.url,
|
||||
origin: 'disk',
|
||||
cover: url
|
||||
console.info( 'Loading song', songDetails.filename );
|
||||
fetch( songDetails.url )
|
||||
.then( res => {
|
||||
if ( res.status === 200 ) {
|
||||
res.blob().then( blob => {
|
||||
console.info( 'Song loaded for processing' );
|
||||
parseBlob( blob )
|
||||
.then( data => {
|
||||
console.info( 'Song metadata processing successful' );
|
||||
|
||||
try {
|
||||
player.findSongOnAppleMusic( data.common.title
|
||||
?? songDetails.filename.split( '.' )[ 0 ] )
|
||||
.then( d => {
|
||||
console.info( 'Apple Music API lookup successful' );
|
||||
let url = d.data.results.songs.data[ 0 ].attributes.artwork.url;
|
||||
|
||||
url = url.replace( '{w}', String( d.data.results.songs.data[ 0 ].attributes.artwork.width ) );
|
||||
url = url.replace( '{h}', String( d.data.results.songs.data[ 0 ].attributes.artwork.height ) );
|
||||
const song: Song = {
|
||||
'artist': d.data.results.songs.data[ 0 ].attributes.artistName,
|
||||
'title': d.data.results.songs.data[ 0 ].attributes.name,
|
||||
'duration': data.format.duration ?? ( d.data.results.songs.data[ 0 ].attributes.durationInMillis / 1000 ),
|
||||
'id': songDetails.url,
|
||||
'origin': 'disk',
|
||||
'cover': url
|
||||
};
|
||||
|
||||
resolve( song );
|
||||
} )
|
||||
.catch( e => {
|
||||
console.info( 'Apple Music API failed' );
|
||||
console.error( e );
|
||||
const song: Song = {
|
||||
'artist': data.common.artist ?? 'Unknown artist',
|
||||
'title': data.common.title ?? 'Unknown song title',
|
||||
'duration': data.format.duration ?? 1000,
|
||||
'id': songDetails.url,
|
||||
'origin': 'disk',
|
||||
'cover': ''
|
||||
};
|
||||
|
||||
resolve( song );
|
||||
} );
|
||||
} catch ( err ) {
|
||||
console.error( err );
|
||||
alert( 'One of your songs was not loadable. (finalization-error)' );
|
||||
reject( err );
|
||||
}
|
||||
resolve( song );
|
||||
} ).catch( e => {
|
||||
} )
|
||||
.catch( e => {
|
||||
console.error( e );
|
||||
const song: Song = {
|
||||
artist: data.common.artist ?? 'Unknown artist',
|
||||
title: data.common.title ?? 'Unknown song title',
|
||||
duration: data.format.duration ?? 1000,
|
||||
id: songDetails.url,
|
||||
origin: 'disk',
|
||||
cover: ''
|
||||
}
|
||||
resolve( song );
|
||||
alert( 'One of your songs was not loadable. (parser-error)' );
|
||||
reject( e );
|
||||
} );
|
||||
} catch ( err ) {
|
||||
console.error( err );
|
||||
alert( 'One of your songs was not loadable. (finalization-error)' )
|
||||
}
|
||||
} ).catch( e => {
|
||||
console.error( e );
|
||||
alert( 'One of your songs was not loadable. (parser-error)' );
|
||||
reject( e );
|
||||
} );
|
||||
} ).catch( e => {
|
||||
console.error( e );
|
||||
alert( 'One of your songs was not loadable. (converter-error)' );
|
||||
reject( e );
|
||||
} );
|
||||
} else {
|
||||
console.error( res.status );
|
||||
alert( 'One of your songs was not loadable. (invalid-response-code)' );
|
||||
}
|
||||
} ).catch( e => {
|
||||
console.error( e );
|
||||
alert( 'One of your songs was not loadable. (could-not-connect)' );
|
||||
reject( e );
|
||||
} );
|
||||
} )
|
||||
.catch( e => {
|
||||
console.error( e );
|
||||
alert( 'One of your songs was not loadable. (converter-error)' );
|
||||
reject( e );
|
||||
} );
|
||||
} else {
|
||||
console.error( res.status );
|
||||
alert( 'One of your songs was not loadable. (invalid-response-code)' );
|
||||
reject( res.status );
|
||||
}
|
||||
} )
|
||||
.catch( e => {
|
||||
console.error( e );
|
||||
alert( 'One of your songs was not loadable. (could-not-connect)' );
|
||||
reject( e );
|
||||
} );
|
||||
} );
|
||||
}
|
||||
};
|
||||
|
||||
const getDetails = () => {
|
||||
const details = player.getPlayingSong();
|
||||
|
||||
currentlyPlayingSongName.value = details.title;
|
||||
coverArt.value = details.cover;
|
||||
currentlyPlayingSongIndex.value = player.getQueueID();
|
||||
playlist.value = player.getQueue();
|
||||
currentlyPlayingSongArtist.value = details.artist;
|
||||
}
|
||||
};
|
||||
|
||||
const playSong = ( id: string ) => {
|
||||
const p = player.getPlaylist();
|
||||
|
||||
currentlyPlayingSongArtist.value = '';
|
||||
coverArt.value = '';
|
||||
currentlyPlayingSongName.value = 'Loading...';
|
||||
stopProgressTracker();
|
||||
|
||||
for ( const s in p ) {
|
||||
if ( p[ s ].id === id ) {
|
||||
player.prepare( parseInt( s ) );
|
||||
@@ -405,24 +524,29 @@
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
let progressTracker: ReturnType<typeof setInterval> = setInterval( () => {}, 1000 );
|
||||
|
||||
clearInterval( progressTracker );
|
||||
let hasReachedEnd = false;
|
||||
let hasStarted = false;
|
||||
|
||||
const startProgressTracker = () => {
|
||||
hasReachedEnd = false;
|
||||
isPlaying.value = true;
|
||||
let playingSong = player.getPlayingSong();
|
||||
|
||||
hasStarted = false;
|
||||
pos.value = 0;
|
||||
progressTracker = setInterval( () => {
|
||||
pos.value = player.getPlaybackPos();
|
||||
|
||||
if ( pos.value > playingSong.duration - 1 && !hasReachedEnd ) {
|
||||
stopProgressTracker();
|
||||
hasReachedEnd = true;
|
||||
|
||||
if ( repeatMode.value === '_one_on' ) {
|
||||
player.goToPos( 0 );
|
||||
setTimeout( () => {
|
||||
@@ -449,11 +573,15 @@
|
||||
}
|
||||
|
||||
const minuteCount = Math.floor( pos.value / 60 );
|
||||
|
||||
nicePlaybackPos.value = minuteCount + ':';
|
||||
|
||||
if ( ( '' + minuteCount ).length === 1 ) {
|
||||
nicePlaybackPos.value = '0' + minuteCount + ':';
|
||||
}
|
||||
|
||||
const secondCount = Math.floor( pos.value - minuteCount * 60 );
|
||||
|
||||
if ( ( '' + secondCount ).length === 1 ) {
|
||||
nicePlaybackPos.value += '0' + secondCount;
|
||||
} else {
|
||||
@@ -462,11 +590,15 @@
|
||||
|
||||
if ( isShowingRemainingTime.value ) {
|
||||
const minuteCounts = Math.floor( ( playingSong.duration - pos.value ) / 60 );
|
||||
|
||||
niceDuration.value = '-' + String( minuteCounts ) + ':';
|
||||
|
||||
if ( ( '' + minuteCounts ).length === 1 ) {
|
||||
niceDuration.value = '-0' + minuteCounts + ':';
|
||||
}
|
||||
|
||||
const secondCounts = Math.floor( ( playingSong.duration - pos.value ) - minuteCounts * 60 );
|
||||
|
||||
if ( ( '' + secondCounts ).length === 1 ) {
|
||||
niceDuration.value += '0' + secondCounts;
|
||||
} else {
|
||||
@@ -474,82 +606,101 @@
|
||||
}
|
||||
}
|
||||
}, 100 );
|
||||
}
|
||||
};
|
||||
|
||||
const prepNiceDurationTime = ( playingSong: Song ) => {
|
||||
duration.value = playingSong.duration;
|
||||
const minuteCounts = Math.floor( ( playingSong.duration ) / 60 );
|
||||
const minuteCounts = Math.floor( playingSong.duration / 60 );
|
||||
|
||||
niceDuration.value = String( minuteCounts ) + ':';
|
||||
|
||||
if ( ( '' + minuteCounts ).length === 1 ) {
|
||||
niceDuration.value = '0' + minuteCounts + ':';
|
||||
}
|
||||
const secondCounts = Math.floor( ( playingSong.duration ) - minuteCounts * 60 );
|
||||
|
||||
const secondCounts = Math.floor( playingSong.duration - minuteCounts * 60 );
|
||||
|
||||
if ( ( '' + secondCounts ).length === 1 ) {
|
||||
niceDuration.value += '0' + secondCounts;
|
||||
} else {
|
||||
niceDuration.value += secondCounts;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const stopProgressTracker = () => {
|
||||
try {
|
||||
clearInterval( progressTracker );
|
||||
} catch ( _ ) { /* empty */ }
|
||||
|
||||
isPlaying.value = false;
|
||||
notificationHandler.emit( 'playback-update', isPlaying.value );
|
||||
}
|
||||
};
|
||||
|
||||
const moveSong = ( move: SongMove ) => {
|
||||
player.moveSong( move );
|
||||
getDetails();
|
||||
notificationHandler.emit( 'playlist-update', playlist.value );
|
||||
}
|
||||
};
|
||||
|
||||
const addNewSongs = async ( songs: ReadFile[] ) => {
|
||||
let n = notifications.value.createNotification( 'Analyzing new songs', 200, 'progress', 'normal' );
|
||||
let n = notifications.value.createNotification(
|
||||
'Analyzing new songs', 200, 'progress', 'normal'
|
||||
);
|
||||
|
||||
playlist.value = player.getQueue();
|
||||
|
||||
for ( let element in songs ) {
|
||||
try {
|
||||
playlist.value.push( await fetchSongData( songs[ element ] ) );
|
||||
} catch ( e ) {
|
||||
console.error( e );
|
||||
}
|
||||
notifications.value.updateNotification( n, `Analyzing new songs (${element}/${songs.length})` );
|
||||
|
||||
notifications.value.updateNotification( n, `Analyzing new songs (${ element }/${ songs.length })` );
|
||||
}
|
||||
|
||||
player.setPlaylist( playlist.value );
|
||||
|
||||
if ( !isPlaying.value ) {
|
||||
player.prepare( 0 );
|
||||
isPlaying.value = true;
|
||||
startProgressTracker();
|
||||
}
|
||||
|
||||
notifications.value.cancelNotification( n );
|
||||
notifications.value.createNotification( 'New songs added', 10, 'ok', 'normal' );
|
||||
notifications.value.createNotification(
|
||||
'New songs added', 10, 'ok', 'normal'
|
||||
);
|
||||
notificationHandler.emit( 'playlist-update', playlist.value );
|
||||
}
|
||||
};
|
||||
|
||||
const addNewSongFromObject = ( song: Song ) => {
|
||||
playlist.value = player.getQueue();
|
||||
playlist.value.push( song );
|
||||
player.setPlaylist( playlist.value );
|
||||
|
||||
if ( !isPlaying.value ) {
|
||||
player.prepare( 0 );
|
||||
isPlaying.value = true;
|
||||
startProgressTracker();
|
||||
}
|
||||
|
||||
notificationHandler.emit( 'playlist-update', playlist.value );
|
||||
}
|
||||
};
|
||||
|
||||
const removeSongFromPlaylist = ( song: number ) => {
|
||||
playlist.value = player.getQueue();
|
||||
playlist.value.splice( song, 1 );
|
||||
player.setPlaylist( playlist.value );
|
||||
|
||||
if ( !isPlaying.value ) {
|
||||
player.prepare( 0 );
|
||||
isPlaying.value = true;
|
||||
startProgressTracker();
|
||||
}
|
||||
|
||||
notificationHandler.emit( 'playlist-update', playlist.value );
|
||||
}
|
||||
};
|
||||
|
||||
const clearPlaylist = () => {
|
||||
playlist.value = [];
|
||||
@@ -562,18 +713,20 @@
|
||||
coverArt.value = '';
|
||||
pos.value = 0;
|
||||
notificationHandler.emit( 'playlist-update', playlist.value );
|
||||
}
|
||||
};
|
||||
|
||||
const sendAdditionalInfo = () => {
|
||||
notifications.value.createNotification( 'Additional song info transmitted', 5, 'ok', 'normal' );
|
||||
notifications.value.createNotification(
|
||||
'Additional song info transmitted', 5, 'ok', 'normal'
|
||||
);
|
||||
notificationHandler.emit( 'playlist-update', playlist.value );
|
||||
}
|
||||
};
|
||||
|
||||
emits( 'playerStateChange', isShowingFullScreenPlayer.value ? 'show' : 'hide' );
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
document.addEventListener( 'keydown', ( e ) => {
|
||||
document.addEventListener( 'keydown', e => {
|
||||
if ( !userStore.isUsingKeyboard ) {
|
||||
if ( e.key === ' ' ) {
|
||||
e.preventDefault();
|
||||
@@ -590,7 +743,7 @@
|
||||
|
||||
const dismissNotification = () => {
|
||||
isShowingWarning.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const popupReturnHandler = ( data: any ) => {
|
||||
if ( currentlyOpenPopup === 'create-share' ) {
|
||||
@@ -601,26 +754,35 @@
|
||||
notificationHandler.emit( 'playback-update', isPlaying.value );
|
||||
notificationHandler.emit( 'playback-start-update', new Date().getTime() - pos.value * 1000 );
|
||||
notificationHandler.emit( 'playlist-update', playlist.value );
|
||||
notifications.value.createNotification( 'Joined share "' + data.roomName + '"!', 5, 'ok', 'normal' );
|
||||
notifications.value.createNotification(
|
||||
'Joined share "' + data.roomName + '"!', 5, 'ok', 'normal'
|
||||
);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
notificationHandler.registerListener( 'tampering-msg', ( _ ) => {
|
||||
notificationHandler.registerListener( 'tampering-msg', _ => {
|
||||
isShowingWarning.value = true;
|
||||
} );
|
||||
} ).catch( e => {
|
||||
if ( e === 'ERR_CONFLICT' ) {
|
||||
notifications.value.createNotification( 'A share with this name exists already!', 5, 'error', 'normal' );
|
||||
control( 'start-share' );
|
||||
} else if ( e === 'ERR_UNAUTHORIZED' ) {
|
||||
console.error( e );
|
||||
localStorage.setItem( 'close-tab', 'true' );
|
||||
logoutErrorNotification = notifications.value.createNotification( 'You appear to have been logged out. Click to log in again!', 20, 'error', 'normal', '/', true );
|
||||
} else {
|
||||
console.error( e );
|
||||
notifications.value.createNotification( 'Could not create share!', 5, 'error', 'normal' );
|
||||
}
|
||||
} );
|
||||
} )
|
||||
.catch( e => {
|
||||
if ( e === 'ERR_CONFLICT' ) {
|
||||
notifications.value.createNotification(
|
||||
'A share with this name exists already!', 5, 'error', 'normal'
|
||||
);
|
||||
control( 'start-share' );
|
||||
} else if ( e === 'ERR_UNAUTHORIZED' ) {
|
||||
console.error( e );
|
||||
localStorage.setItem( 'close-tab', 'true' );
|
||||
logoutErrorNotification = notifications.value.createNotification(
|
||||
'You appear to have been logged out. Click to log in again!', 20, 'error', 'normal', '/', true
|
||||
);
|
||||
} else {
|
||||
console.error( e );
|
||||
notifications.value.createNotification(
|
||||
'Could not create share!', 5, 'error', 'normal'
|
||||
);
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener( 'beforeunload', async () => {
|
||||
await notificationHandler.disconnect();
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<template>
|
||||
<div class="playlists">
|
||||
<h3 style="width: fit-content;">Your playlists</h3>
|
||||
<h3 style="width: fit-content;">
|
||||
Your playlists
|
||||
</h3>
|
||||
<div v-if="( $props.playlists ? $props.playlists.length < 1 : true ) && $props.isLoggedIn">
|
||||
Loading...
|
||||
<!-- TODO: Make prettier -->
|
||||
@@ -8,12 +10,27 @@
|
||||
<div v-else-if="!$props.isLoggedIn" class="not-logged-in">
|
||||
<p>You are not logged into Apple Music. We therefore can't show you your playlists. <a href="" title="Refreshes the page, allowing you to log in">Change that</a></p>
|
||||
<p>Use the button below to load songs from your local disk</p>
|
||||
<input class="pl-loader-button" type="file" multiple="true" accept="audio/*" id="pl-loader"><br>
|
||||
<button @click="loadPlaylistFromDisk()" class="pl-loader-button" id="load-button">Load</button>
|
||||
<p v-if="!hasSelectedSongs">Please select at least one song to proceed!</p>
|
||||
<input
|
||||
id="pl-loader"
|
||||
class="pl-loader-button"
|
||||
type="file"
|
||||
multiple="true"
|
||||
accept="audio/*"
|
||||
><br>
|
||||
<button id="load-button" class="pl-loader-button" @click="loadPlaylistFromDisk()">
|
||||
Load
|
||||
</button>
|
||||
<p v-if="!hasSelectedSongs">
|
||||
Please select at least one song to proceed!
|
||||
</p>
|
||||
</div>
|
||||
<div class="playlist-wrapper">
|
||||
<div v-for="pl in $props.playlists" v-bind:key="pl.id" class="playlist" @click="selectPlaylist( pl.id )">
|
||||
<div
|
||||
v-for="pl in $props.playlists"
|
||||
:key="pl.id"
|
||||
class="playlist"
|
||||
@click="selectPlaylist( pl.id )"
|
||||
>
|
||||
{{ pl.attributes.name }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -21,8 +38,13 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { ReadFile } from '@/scripts/song';
|
||||
import { ref } from 'vue';
|
||||
import type {
|
||||
ReadFile
|
||||
} from '@/scripts/song';
|
||||
import {
|
||||
ref
|
||||
} from 'vue';
|
||||
|
||||
const hasSelectedSongs = ref( true );
|
||||
|
||||
defineProps( {
|
||||
@@ -41,22 +63,31 @@
|
||||
const loadPlaylistFromDisk = () => {
|
||||
const fileURLList: ReadFile[] = [];
|
||||
const allFiles = ( document.getElementById( 'pl-loader' ) as HTMLInputElement ).files ?? [];
|
||||
|
||||
if ( allFiles.length > 0 ) {
|
||||
hasSelectedSongs.value = true;
|
||||
|
||||
for ( let file = 0; file < allFiles.length; file++ ) {
|
||||
fileURLList.push( { 'url': URL.createObjectURL( allFiles[ file ] ), 'filename': allFiles[ file ].name } );
|
||||
fileURLList.push( {
|
||||
'url': URL.createObjectURL( allFiles[ file ] ),
|
||||
'filename': allFiles[ file ].name
|
||||
} );
|
||||
}
|
||||
|
||||
emits( 'custom-playlist', fileURLList );
|
||||
} else {
|
||||
hasSelectedSongs.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const emits = defineEmits( [ 'selected-playlist', 'custom-playlist' ] );
|
||||
const emits = defineEmits( [
|
||||
'selected-playlist',
|
||||
'custom-playlist'
|
||||
] );
|
||||
|
||||
const selectPlaylist = ( id: string ) => {
|
||||
emits( 'selected-playlist', id );
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -555,9 +555,10 @@ class MusicKitJSWrapper {
|
||||
'types': [ 'songs' ],
|
||||
};
|
||||
|
||||
this.musicKit.api.music( 'v1/catalog/ch/search', queryParameters ).then( results => {
|
||||
resolve( results );
|
||||
} )
|
||||
this.musicKit.api.music( 'v1/catalog/ch/search', queryParameters )
|
||||
.then( results => {
|
||||
resolve( results );
|
||||
} )
|
||||
.catch( e => {
|
||||
console.error( e );
|
||||
reject( e );
|
||||
|
||||
3671
backend/package-lock.json
generated
3671
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user