Skip to content

Commit 97a861a

Browse files
committed
Add spotify player
1 parent eeaa41b commit 97a861a

File tree

9 files changed

+328
-103
lines changed

9 files changed

+328
-103
lines changed

components.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@ declare module 'vue' {
1313
'AtStyle.ce': typeof import('./src/components/custom-elements/AtStyle.ce.vue')['default']
1414
'AtWebStylesheet.ce': typeof import('./src/components/custom-elements/AtWebStylesheet.ce.vue')['default']
1515
'AtWebTitle.ce': typeof import('./src/components/custom-elements/AtWebTitle.ce.vue')['default']
16+
copy: typeof import('./src/components/custom-elements/AtAnchor.ce copy.vue')['default']
1617
MarkdownRenderer: typeof import('./src/components/MarkdownRenderer.vue')['default']
1718
MModal: typeof import('./src/components/MModal.vue')['default']
1819
MonacoEditor: typeof import('./src/components/MonacoEditor.vue')['default']
1920
'OmitVanillaCss.ce': typeof import('./src/components/custom-elements/OmitVanillaCss.ce.vue')['default']
2021
RouterLink: typeof import('vue-router')['RouterLink']
2122
RouterView: typeof import('vue-router')['RouterView']
2223
SignInGate: typeof import('./src/components/SignInGate.vue')['default']
24+
SpotifyPlayer: typeof import('./src/components/custom-elements/SpotifyPlayer.vue')['default']
2325
VaButton: typeof import('vuestic-ui')['VaButton']
2426
'WebampPlayer.ce': typeof import('./src/components/custom-elements/WebampPlayer.ce.vue')['default']
2527
}

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"@atproto/syntax": "^0.3.1",
2828
"@mdx-js/mdx": "^3.1.0",
2929
"@noble/hashes": "^1.6.1",
30+
"@vue/reactivity": "3.5.13",
3031
"@vueuse/core": "^12.0.0",
3132
"bluesky-post-embed": "^1.0.5",
3233
"bluesky-profile-card-embed": "^1.0.0",
@@ -40,7 +41,6 @@
4041
"mime-type": "^5.0.0",
4142
"monaco-themes": "^0.4.4",
4243
"music-metadata": "^10.6.4",
43-
"music-metadata-browser": "^2.5.11",
4444
"path-browserify": "^1.0.1",
4545
"rehype": "^13.0.2",
4646
"uint8arrays": "^5.1.0",
@@ -72,6 +72,7 @@
7272
"sass-embedded": "^1.80.2",
7373
"typescript": "~5.6.3",
7474
"vite": "^6.0.1",
75+
"vite-plugin-https-imports": "^0.1.0",
7576
"vite-plugin-node-polyfills": "^0.22.0",
7677
"vite-plugin-vue-devtools": "^7.6.5",
7778
"vue-tsc": "^2.1.10"

pnpm-lock.yaml

Lines changed: 240 additions & 101 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<script setup lang="ts">
2+
import { type EmbedController, useSpotifyIFrameAPI } from '@/lib/spotify';
3+
import { useScriptTag, watchImmediate } from '@vueuse/core';
4+
import { onBeforeUnmount, onMounted, onUnmounted, shallowRef, useTemplateRef } from 'vue';
5+
6+
const props = defineProps<{
7+
uri?: string;
8+
width?: number;
9+
height?: number;
10+
}>();
11+
12+
const spotifyElement = useTemplateRef('spotifyElement');
13+
const IFrameAPI = useSpotifyIFrameAPI();
14+
const EmbedController = shallowRef<EmbedController>();
15+
16+
onMounted(() => {
17+
watchImmediate(IFrameAPI, IFrameAPI => {
18+
if (!IFrameAPI) return;
19+
20+
IFrameAPI.createController(spotifyElement.value!, {
21+
uri: props.uri,
22+
width: props.width,
23+
height: props.height,
24+
}, controller => {
25+
EmbedController.value = controller;
26+
});
27+
});
28+
});
29+
30+
onBeforeUnmount(() => {
31+
EmbedController.value?.destroy();
32+
});
33+
34+
</script>
35+
36+
<template>
37+
<component :is="'script'"><slot></slot></component>
38+
<div v-bind="$attrs" ref="spotifyElement"></div>
39+
</template>

src/lib/markdown/components.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import AtWebTitle from '@/components/custom-elements/AtWebTitle.ce.vue';
1414
import { type Slot } from 'vue';
1515
import type { JSX } from 'vue/jsx-runtime';
1616
import WebampPlayer from '@/components/custom-elements/WebampPlayer.ce.vue';
17+
import SpotifyPlayer from '@/components/custom-elements/SpotifyPlayer.vue';
1718

1819
type Props = Record<string, any>;
1920

@@ -62,5 +63,6 @@ export const components: MDXComponents = {
6263
BlueskyProfileCard: 'bluesky-profile-card',
6364
BlueskyProfileFeed: 'bluesky-profile-feed',
6465
Webamp: WebampPlayer,
66+
Spotify: SpotifyPlayer,
6567
...(extraComponents as unknown as MDXComponents)
6668
};

src/lib/spotify.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { useScriptTag } from '@vueuse/core';
2+
import { shallowRef } from 'vue';
3+
4+
export interface IFrameAPI {
5+
createController(element: Element, options: { uri?: string, width?: number, height?: number }, callback: (EmbedController: EmbedController) => void): void;
6+
}
7+
export interface EmbedController {
8+
loadUri(spotifyUri: string, preferVideo?: boolean, startAt?: number): void;
9+
play(): void;
10+
pause(): void;
11+
resume(): void;
12+
togglePlay(): void;
13+
restart(): void;
14+
seek(seconds: number): void;
15+
destroy(): void;
16+
17+
addListener(event: 'ready', callback: () => void): void;
18+
addListener(event: 'playback_update', callback: (e: {data: {isPaused: boolean, isBuffering: boolean, duration: number, position: number }}) => void): void;
19+
}
20+
21+
declare global {
22+
interface Window {
23+
onSpotifyIframeApiReady: (IFrameAPI: IFrameAPI) => void;
24+
}
25+
}
26+
27+
const IFrameAPI = shallowRef<IFrameAPI>();
28+
window.onSpotifyIframeApiReady = api => {
29+
IFrameAPI.value = api;
30+
};
31+
const { load: loadSpotify } = useScriptTag('https://open.spotify.com/embed/iframe-api/v1', () => {}, { manual: true });
32+
loadSpotify();
33+
34+
export function useSpotifyIFrameAPI() {
35+
return IFrameAPI;
36+
}

src/webamp.d.ts renamed to src/modules.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@ declare module '#webamp-lazy' {
55
import Webamp from '@/external/webamp/built/types/js/webampLazy';
66
export default Webamp;
77
}
8+
9+
declare module 'https://open.spotify.com/embed/iframe-api/v1';

tsconfig.node.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
"module": "ESNext",
1616
"moduleResolution": "Bundler",
17-
"types": ["node"]
17+
"types": ["node"],
18+
"esModuleInterop": true
1819
}
1920
}

vite.config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import vueJsx from '@vitejs/plugin-vue-jsx';
66
import vueDevTools from 'vite-plugin-vue-devtools';
77
import Components from 'unplugin-vue-components/vite';
88
import { nodePolyfills } from 'vite-plugin-node-polyfills';
9+
import httpsImports from 'vite-plugin-https-imports';
910

1011
// https://vite.dev/config/
1112
export default defineConfig({
@@ -30,6 +31,8 @@ export default defineConfig({
3031
],
3132
}),
3233
nodePolyfills(),
34+
// @ts-expect-error broken commonjs
35+
(httpsImports.default as typeof httpsImports)(),
3336

3437
// // @quasar/plugin-vite options list:
3538
// // https://github.com/quasarframework/quasar/blob/dev/vite-plugin/index.d.ts

0 commit comments

Comments
 (0)