feat: currently playing + volume + seek position + toggle pause
This commit is contained in:
@@ -2,3 +2,5 @@
|
||||
package-lock.json
|
||||
pnpm-lock.yaml
|
||||
yarn.lock
|
||||
src/lib/proto/
|
||||
src/lib/components/ui/
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"format": "prettier --write ."
|
||||
"format": "prettier --write .",
|
||||
"build:proto": "bunx protoc --ts_out ./src/lib/proto/ --proto_path protos protos/*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "^3.0.0",
|
||||
|
||||
@@ -17,4 +17,5 @@ message Track {
|
||||
string name = 2;
|
||||
string artist_name = 3;
|
||||
uint64 artist_id = 4;
|
||||
uint64 duration = 5;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
syntax = "proto3";
|
||||
|
||||
import 'google/protobuf/empty.proto';
|
||||
import 'library.proto';
|
||||
|
||||
package player;
|
||||
|
||||
service Player {
|
||||
rpc PlayTrack(PlayTrackRequest) returns (PlayTrackResponse);
|
||||
rpc ResumeTrack(google.protobuf.Empty) returns (google.protobuf.Empty);
|
||||
rpc PauseTrack(google.protobuf.Empty) returns (google.protobuf.Empty);
|
||||
rpc ResumeTrack(google.protobuf.Empty) returns (PauseState);
|
||||
rpc PauseTrack(google.protobuf.Empty) returns (PauseState);
|
||||
rpc TogglePause(google.protobuf.Empty) returns (PauseState);
|
||||
rpc GetStatus(google.protobuf.Empty) returns (stream PlayerStatus);
|
||||
rpc SeekPosition(SeekPositionRequest) returns (SeekPositionResponse);
|
||||
rpc SetVolume(SetVolumeRequest) returns (SetVolumeResponse);
|
||||
}
|
||||
|
||||
message PlayTrackRequest {
|
||||
@@ -15,4 +20,33 @@ message PlayTrackRequest {
|
||||
}
|
||||
|
||||
message PlayTrackResponse {
|
||||
library.Track track = 1;
|
||||
uint64 position = 2;
|
||||
}
|
||||
|
||||
message PlayerStatus {
|
||||
optional library.Track currently_playing = 1;
|
||||
bool is_paused = 2;
|
||||
float volume = 3;
|
||||
uint64 progress = 4;
|
||||
}
|
||||
|
||||
message PauseState {
|
||||
bool is_paused = 1;
|
||||
}
|
||||
|
||||
message SeekPositionRequest {
|
||||
uint64 position = 1;
|
||||
}
|
||||
|
||||
message SeekPositionResponse {
|
||||
uint64 position = 1;
|
||||
}
|
||||
|
||||
message SetVolumeRequest {
|
||||
float volume = 1;
|
||||
}
|
||||
|
||||
message SetVolumeResponse {
|
||||
float volume = 1;
|
||||
}
|
||||
|
||||
18
src/app.css
18
src/app.css
@@ -26,10 +26,10 @@
|
||||
|
||||
--sidebar-background: 0 0% 98%;
|
||||
--sidebar-foreground: 240 5.3% 26.1%;
|
||||
--sidebar-primary: 240 5.9% 10%;
|
||||
--sidebar-primary-foreground: 0 0% 98%;
|
||||
--sidebar-accent: 240 4.8% 95.9%;
|
||||
--sidebar-accent-foreground: 240 5.9% 10%;
|
||||
--sidebar-primary: 217.2 91.2% 59.8%;
|
||||
--sidebar-primary-foreground: 222.2 47.4% 11.2%;
|
||||
--sidebar-accent: 217.2 91.2% 59.8%;
|
||||
--sidebar-accent-foreground: 222.2 47.4% 11.2%;
|
||||
--sidebar-border: 220 13% 91%;
|
||||
--sidebar-ring: 217.2 91.2% 59.8%;
|
||||
|
||||
@@ -56,12 +56,12 @@
|
||||
--input: 217.2 32.6% 17.5%;
|
||||
--ring: 224.3 76.3% 48%;
|
||||
|
||||
--sidebar-background: 240 5.9% 10%;
|
||||
--sidebar-background: 223 84% 3%;
|
||||
--sidebar-foreground: 240 4.8% 95.9%;
|
||||
--sidebar-primary: 224.3 76.3% 48%;
|
||||
--sidebar-primary-foreground: 0 0% 100%;
|
||||
--sidebar-accent: 240 3.7% 15.9%;
|
||||
--sidebar-accent-foreground: 240 4.8% 95.9%;
|
||||
--sidebar-primary: 217.2 91.2% 59.8%;
|
||||
--sidebar-primary-foreground: 222.2 47.4% 11.2%;
|
||||
--sidebar-accent: 217.2 91.2% 59.8%;
|
||||
--sidebar-accent-foreground: 222.2 47.4% 11.2%;
|
||||
--sidebar-border: 240 3.7% 15.9%;
|
||||
--sidebar-ring: 217.2 91.2% 59.8%;
|
||||
}
|
||||
|
||||
5
src/hooks.client.ts
Normal file
5
src/hooks.client.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { GrpcWebFetchTransport } from '@protobuf-ts/grpcweb-transport';
|
||||
|
||||
export const protoTransport = new GrpcWebFetchTransport({
|
||||
baseUrl: 'http://[::1]:39993'
|
||||
});
|
||||
@@ -9,6 +9,9 @@
|
||||
<Sidebar.Menu>
|
||||
<Sidebar.MenuItem>
|
||||
<Sidebar.MenuButton isActive={$page.url.pathname === '/'} class="transition-all">
|
||||
{#snippet tooltipContent()}
|
||||
Home
|
||||
{/snippet}
|
||||
{#snippet child({ props })}
|
||||
<a href="/" {...props}>
|
||||
<Home />
|
||||
@@ -25,6 +28,9 @@
|
||||
<Sidebar.Menu>
|
||||
<Sidebar.MenuItem>
|
||||
<Sidebar.MenuButton isActive={$page.url.pathname === '/songs'} class="transition-all">
|
||||
{#snippet tooltipContent()}
|
||||
Songs
|
||||
{/snippet}
|
||||
{#snippet child({ props })}
|
||||
<a href="/songs" {...props}>
|
||||
<Music2 />
|
||||
@@ -35,6 +41,9 @@
|
||||
</Sidebar.MenuItem>
|
||||
<Sidebar.MenuItem>
|
||||
<Sidebar.MenuButton isActive={$page.url.pathname === '/albums'} class="transition-all">
|
||||
{#snippet tooltipContent()}
|
||||
Albums
|
||||
{/snippet}
|
||||
{#snippet child({ props })}
|
||||
<a href="/albums" {...props}>
|
||||
<Library />
|
||||
@@ -45,6 +54,9 @@
|
||||
</Sidebar.MenuItem>
|
||||
<Sidebar.MenuItem>
|
||||
<Sidebar.MenuButton isActive={$page.url.pathname === '/playlists'} class="transition-all">
|
||||
{#snippet tooltipContent()}
|
||||
Playlists
|
||||
{/snippet}
|
||||
{#snippet child({ props })}
|
||||
<a href="/playlists" {...props}>
|
||||
<ListMusic />
|
||||
@@ -60,6 +72,9 @@
|
||||
<Sidebar.Menu>
|
||||
<Sidebar.MenuItem>
|
||||
<Sidebar.MenuButton isActive={$page.url.pathname === '/settings'} class="transition-all">
|
||||
{#snippet tooltipContent()}
|
||||
Settings
|
||||
{/snippet}
|
||||
{#snippet child({ props })}
|
||||
<a href="/settings" {...props}>
|
||||
<Settings />
|
||||
|
||||
@@ -16,65 +16,117 @@
|
||||
import { Slider } from '$lib/components/ui/slider';
|
||||
import * as Popover from '$lib/components/ui/popover';
|
||||
import { Separator } from '$lib/components/ui/separator';
|
||||
import { currentlyPlaying, volume } from '$lib/stores/player';
|
||||
import { getPlayerState } from '$lib/player.svelte';
|
||||
import { enhance } from '$app/forms';
|
||||
import { getCoverUrl } from '$lib/covers';
|
||||
import type { SubmitFunction } from '../../../routes/player/$types';
|
||||
|
||||
dayjs.extend(duration);
|
||||
|
||||
let progressValue = $state(0);
|
||||
const player = getPlayerState();
|
||||
|
||||
let isPlaying = $state(false);
|
||||
let trackDuration = $derived(
|
||||
player.currentlyPlaying ? Number(player.currentlyPlaying.duration) : 0
|
||||
);
|
||||
|
||||
/* async function seekProgressValue(e: MouseEvent) {
|
||||
if (!$currentlyPlaying) {
|
||||
let mouse = $state({
|
||||
offsetX: 0,
|
||||
offsetY: 0
|
||||
});
|
||||
|
||||
let progressBar = $state<HTMLElement | null>(null);
|
||||
let volumeForm = $state<HTMLFormElement | null>(null);
|
||||
|
||||
const seekProgressValue: SubmitFunction = ({ formData, cancel }) => {
|
||||
if (!player.currentlyPlaying || !progressBar) {
|
||||
cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
const target = e.currentTarget! as HTMLElement;
|
||||
const _targetProgress = Math.max(0.0, Math.min(1.0, e.offsetX / target.offsetWidth));
|
||||
} */
|
||||
const targetProgress = Math.round(
|
||||
Math.max(0.0, Math.min(1.0, mouse.offsetX / progressBar.offsetWidth)) *
|
||||
Number(player.currentlyPlaying.duration)
|
||||
);
|
||||
|
||||
formData.append('position', targetProgress.toString());
|
||||
|
||||
return async ({ update, result }) => {
|
||||
await update({
|
||||
invalidateAll: false
|
||||
});
|
||||
|
||||
if (result.type === 'success' && result.data && 'position' in result.data) {
|
||||
player.progress = result.data.position;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
async function skipToQueueIndex(index: number) {
|
||||
console.log(`SKIP TO QUEUE INDEX ${index}`);
|
||||
}
|
||||
|
||||
let volumeValue = {
|
||||
get value() {
|
||||
return [$volume];
|
||||
},
|
||||
set value(v: number[]) {
|
||||
$volume = v[0];
|
||||
// TODO: Send message to player
|
||||
const submitTogglePause: SubmitFunction = async () => {
|
||||
return async ({ update, result }) => {
|
||||
await update({
|
||||
invalidateAll: false
|
||||
});
|
||||
|
||||
if (result.type === 'success' && result.data && 'isPaused' in result.data) {
|
||||
player.isPaused = result.data.isPaused;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
function onMouseMove(e: MouseEvent) {
|
||||
mouse.offsetX = e.offsetX;
|
||||
mouse.offsetY = e.offsetY;
|
||||
}
|
||||
|
||||
let volume = $state({
|
||||
get value() {
|
||||
return [player.volume];
|
||||
},
|
||||
set value(v: number[]) {
|
||||
player.volume = v[0];
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<footer class="border border-x-0 border-t border-border/40 bg-background/95 px-4">
|
||||
<nav class="my-2 grid w-full grid-cols-5 items-center">
|
||||
<div class="col-span-1 flex items-end gap-2">
|
||||
{#if $currentlyPlaying}
|
||||
{#if player.currentlyPlaying}
|
||||
<img
|
||||
class="w-16 rounded-md shadow-2xl shadow-primary/50"
|
||||
src="https://i.scdn.co/image/ab67616d0000b2732c0ead8ce0dd1c6e2fca817f"
|
||||
alt={$currentlyPlaying}
|
||||
class="aspect-square w-16 rounded-md shadow-2xl shadow-primary/50"
|
||||
src={getCoverUrl(player.currentlyPlaying.hash)}
|
||||
alt={player.currentlyPlaying.name}
|
||||
/>
|
||||
<div class="space-y-1 text-sm">
|
||||
<h3 class="font-medium leading-none">Song name</h3>
|
||||
<p class="text-xs text-muted-foreground">Artist name</p>
|
||||
<h3 class="font-medium leading-none">{player.currentlyPlaying.name}</h3>
|
||||
<p class="text-xs text-muted-foreground">{player.currentlyPlaying.artistName}</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="h-16 w-16 rounded-md bg-muted shadow-2xl shadow-primary/50"></div>
|
||||
{/if}
|
||||
</div>
|
||||
<form class="col-span-3 flex justify-center gap-1" method="POST" use:enhance>
|
||||
<form
|
||||
class="col-span-3 flex justify-center gap-1"
|
||||
method="POST"
|
||||
use:enhance={submitTogglePause}
|
||||
>
|
||||
<Button variant="outline" size="icon">
|
||||
<SkipBack />
|
||||
</Button>
|
||||
<Button type="submit" formaction="/player?/pause" variant="outline" size="icon">
|
||||
{#if isPlaying}
|
||||
<Pause />
|
||||
{:else}
|
||||
<Button
|
||||
type="submit"
|
||||
formaction="/player?/{player.isPaused ? 'resume' : 'pause'}"
|
||||
variant="outline"
|
||||
size="icon"
|
||||
>
|
||||
{#if player.isPaused}
|
||||
<Play />
|
||||
{:else}
|
||||
<Pause />
|
||||
{/if}
|
||||
</Button>
|
||||
<Button variant="outline" size="icon">
|
||||
@@ -129,32 +181,67 @@
|
||||
|
||||
<div class="flex grow flex-row items-center gap-4">
|
||||
<div class="min-w-6 text-muted-foreground">
|
||||
{#if $volume <= 0}
|
||||
{#if player.volume <= 0.0}
|
||||
<VolumeX class="size-full" />
|
||||
{:else if $volume < 50.0}
|
||||
{:else if player.volume < 0.5}
|
||||
<Volume1 class="size-full" />
|
||||
{:else}
|
||||
<Volume2 class="size-full" />
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<Slider class="w-full" min={0} max={1} step={0.01} bind:value={volumeValue.value} />
|
||||
</div>
|
||||
<form
|
||||
bind:this={volumeForm}
|
||||
class="flex-1"
|
||||
method="POST"
|
||||
action="/player?/volume"
|
||||
use:enhance
|
||||
>
|
||||
<input type="hidden" name="volume" value={volume.value[0]} />
|
||||
<Slider
|
||||
class="w-full"
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
bind:value={volume.value}
|
||||
onValueChange={() => {
|
||||
volumeForm?.requestSubmit();
|
||||
}}
|
||||
onValueCommit={() => {
|
||||
volumeForm?.requestSubmit();
|
||||
}}
|
||||
/>
|
||||
</form>
|
||||
<div class="min-w-10 self-center">
|
||||
<p class="text-sm text-muted-foreground">
|
||||
{Math.round($volume * 100.0)}%
|
||||
{Math.round(player.volume * 100.0)}%
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="mb-2 flex flex-row items-center gap-2">
|
||||
<span class="w-12 text-left text-sm">{dayjs.duration(0, 'seconds').format('mm:ss')}</span>
|
||||
<button class="w-full">
|
||||
{#key progressValue}
|
||||
<Progress class="pointer-events-none w-full" value={progressValue} max={100} />
|
||||
{/key}
|
||||
<span class="w-12 text-left text-sm"
|
||||
>{dayjs.duration(Number(player.progress), 'milliseconds').format('mm:ss')}</span
|
||||
>
|
||||
<form
|
||||
class="w-full"
|
||||
method="POST"
|
||||
action="/player?/seek"
|
||||
use:enhance={seekProgressValue}
|
||||
bind:this={progressBar}
|
||||
>
|
||||
<button class="w-full" type="submit">
|
||||
<Progress
|
||||
class="pointer-events-none h-3 w-full"
|
||||
value={Number(player.progress)}
|
||||
max={player.currentlyPlaying ? Number(player.currentlyPlaying.duration) : 1}
|
||||
/>
|
||||
</button>
|
||||
<span class="w-12 text-right text-sm">{dayjs.duration(0, 'seconds').format('mm:ss')}</span>
|
||||
</form>
|
||||
<span class="w-12 text-right text-sm"
|
||||
>{dayjs.duration(trackDuration, 'milliseconds').format('mm:ss')}</span
|
||||
>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<svelte:window onmousemove={onMouseMove} />
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { enhance } from '$app/forms';
|
||||
import { getCoverUrl } from '$lib/covers';
|
||||
import { getPlayerState } from '$lib/player.svelte';
|
||||
|
||||
// import { AudioLines } from 'lucide-svelte';
|
||||
import type { Song } from '$lib/song';
|
||||
import type { SubmitFunction } from '../../../routes/songs/[hash]/$types';
|
||||
|
||||
interface Props {
|
||||
song: Song;
|
||||
@@ -11,15 +13,30 @@
|
||||
|
||||
let { song }: Props = $props();
|
||||
|
||||
async function playSong(e: MouseEvent) {
|
||||
e.preventDefault();
|
||||
// TODO: Play song
|
||||
const player = getPlayerState();
|
||||
|
||||
const submitPlaySong: SubmitFunction = async () => {
|
||||
return async ({ update, result }) => {
|
||||
await update({
|
||||
invalidateAll: false
|
||||
});
|
||||
|
||||
if (result.type === 'success' && result.data) {
|
||||
player.currentlyPlaying = result.data.track ?? null;
|
||||
player.progress = result.data.position;
|
||||
}
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<form class="flex flex-col gap-3" method="POST" action="/songs/{song.hash}?/play" use:enhance>
|
||||
<form
|
||||
class="flex flex-col gap-3"
|
||||
method="POST"
|
||||
action="/songs/{song.hash}?/play"
|
||||
use:enhance={submitPlaySong}
|
||||
>
|
||||
<div class="relative">
|
||||
<button type="submit" class="relative overflow-hidden rounded-lg" oncontextmenu={playSong}>
|
||||
<button type="submit" class="relative overflow-hidden rounded-lg">
|
||||
<img
|
||||
class="aspect-square w-auto transition-all duration-150 hover:scale-105 hover:saturate-150"
|
||||
src={getCoverUrl(song.hash)}
|
||||
|
||||
50
src/lib/player.svelte.ts
Normal file
50
src/lib/player.svelte.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import type { Track } from './proto/library';
|
||||
import type { PlayerStatus } from './proto/player';
|
||||
import { getContext, setContext } from 'svelte';
|
||||
import { PlayerClient } from './proto/player.client';
|
||||
import { protoTransport } from '../hooks.client';
|
||||
|
||||
class PlayerState {
|
||||
volume = $state(0);
|
||||
currentlyPlaying = $state<Track | null>(null);
|
||||
progress = $state<bigint>(0n);
|
||||
isPaused = $state(false);
|
||||
#abortContoller: AbortController | null = null;
|
||||
|
||||
constructor() {
|
||||
const client = new PlayerClient(protoTransport);
|
||||
this.#abortContoller = new AbortController();
|
||||
|
||||
const stream = client.getStatus(
|
||||
{},
|
||||
{
|
||||
abort: this.#abortContoller.signal
|
||||
}
|
||||
);
|
||||
|
||||
stream.responses.onMessage((status) => this.applyStatus(status));
|
||||
}
|
||||
|
||||
applyStatus(status: PlayerStatus) {
|
||||
this.volume = status.volume;
|
||||
this.progress = status.progress;
|
||||
this.currentlyPlaying = status.currentlyPlaying ?? null;
|
||||
this.isPaused = status.isPaused;
|
||||
}
|
||||
|
||||
abort() {
|
||||
if (this.#abortContoller !== null) {
|
||||
this.#abortContoller.abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const PLAYER_KEY = Symbol('GROOVE_PLAYER');
|
||||
|
||||
export function setPlayerState() {
|
||||
return setContext(PLAYER_KEY, new PlayerState());
|
||||
}
|
||||
|
||||
export function getPlayerState() {
|
||||
return getContext<ReturnType<typeof setPlayerState>>(PLAYER_KEY);
|
||||
}
|
||||
17
src/lib/proto.ts
Normal file
17
src/lib/proto.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
export function serializable<F, T extends object = object>(data: T): F {
|
||||
const obj = {};
|
||||
|
||||
for (const key in data) {
|
||||
const value = data[key];
|
||||
|
||||
if (typeof value === 'object') {
|
||||
/// @ts-ignore
|
||||
obj[key] = serializable(value);
|
||||
} else {
|
||||
/// @ts-ignore
|
||||
obj[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return obj as unknown as F;
|
||||
}
|
||||
@@ -41,6 +41,10 @@ export interface Track {
|
||||
* @generated from protobuf field: uint64 artist_id = 4;
|
||||
*/
|
||||
artistId: bigint;
|
||||
/**
|
||||
* @generated from protobuf field: uint64 duration = 5;
|
||||
*/
|
||||
duration: bigint;
|
||||
}
|
||||
// @generated message type with reflection information, may provide speed optimized methods
|
||||
class TrackList$Type extends MessageType<TrackList> {
|
||||
@@ -122,6 +126,13 @@ class Track$Type extends MessageType<Track> {
|
||||
kind: 'scalar',
|
||||
T: 4 /*ScalarType.UINT64*/,
|
||||
L: 0 /*LongType.BIGINT*/
|
||||
},
|
||||
{
|
||||
no: 5,
|
||||
name: 'duration',
|
||||
kind: 'scalar',
|
||||
T: 4 /*ScalarType.UINT64*/,
|
||||
L: 0 /*LongType.BIGINT*/
|
||||
}
|
||||
]);
|
||||
}
|
||||
@@ -131,6 +142,7 @@ class Track$Type extends MessageType<Track> {
|
||||
message.name = '';
|
||||
message.artistName = '';
|
||||
message.artistId = 0n;
|
||||
message.duration = 0n;
|
||||
if (value !== undefined) reflectionMergePartial<Track>(this, message, value);
|
||||
return message;
|
||||
}
|
||||
@@ -157,6 +169,9 @@ class Track$Type extends MessageType<Track> {
|
||||
case /* uint64 artist_id */ 4:
|
||||
message.artistId = reader.uint64().toBigInt();
|
||||
break;
|
||||
case /* uint64 duration */ 5:
|
||||
message.duration = reader.uint64().toBigInt();
|
||||
break;
|
||||
default:
|
||||
let u = options.readUnknownField;
|
||||
if (u === 'throw')
|
||||
@@ -190,6 +205,8 @@ class Track$Type extends MessageType<Track> {
|
||||
writer.tag(3, WireType.LengthDelimited).string(message.artistName);
|
||||
/* uint64 artist_id = 4; */
|
||||
if (message.artistId !== 0n) writer.tag(4, WireType.Varint).uint64(message.artistId);
|
||||
/* uint64 duration = 5; */
|
||||
if (message.duration !== 0n) writer.tag(5, WireType.Varint).uint64(message.duration);
|
||||
let u = options.writeUnknownFields;
|
||||
if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||
return writer;
|
||||
|
||||
@@ -4,6 +4,13 @@
|
||||
import type { RpcTransport } from '@protobuf-ts/runtime-rpc';
|
||||
import type { ServiceInfo } from '@protobuf-ts/runtime-rpc';
|
||||
import { Player } from './player';
|
||||
import type { SetVolumeResponse } from './player';
|
||||
import type { SetVolumeRequest } from './player';
|
||||
import type { SeekPositionResponse } from './player';
|
||||
import type { SeekPositionRequest } from './player';
|
||||
import type { PlayerStatus } from './player';
|
||||
import type { ServerStreamingCall } from '@protobuf-ts/runtime-rpc';
|
||||
import type { PauseState } from './player';
|
||||
import type { Empty } from './google/protobuf/empty';
|
||||
import { stackIntercept } from '@protobuf-ts/runtime-rpc';
|
||||
import type { PlayTrackResponse } from './player';
|
||||
@@ -22,13 +29,35 @@ export interface IPlayerClient {
|
||||
options?: RpcOptions
|
||||
): UnaryCall<PlayTrackRequest, PlayTrackResponse>;
|
||||
/**
|
||||
* @generated from protobuf rpc: ResumeTrack(google.protobuf.Empty) returns (google.protobuf.Empty);
|
||||
* @generated from protobuf rpc: ResumeTrack(google.protobuf.Empty) returns (player.PauseState);
|
||||
*/
|
||||
resumeTrack(input: Empty, options?: RpcOptions): UnaryCall<Empty, Empty>;
|
||||
resumeTrack(input: Empty, options?: RpcOptions): UnaryCall<Empty, PauseState>;
|
||||
/**
|
||||
* @generated from protobuf rpc: PauseTrack(google.protobuf.Empty) returns (google.protobuf.Empty);
|
||||
* @generated from protobuf rpc: PauseTrack(google.protobuf.Empty) returns (player.PauseState);
|
||||
*/
|
||||
pauseTrack(input: Empty, options?: RpcOptions): UnaryCall<Empty, Empty>;
|
||||
pauseTrack(input: Empty, options?: RpcOptions): UnaryCall<Empty, PauseState>;
|
||||
/**
|
||||
* @generated from protobuf rpc: TogglePause(google.protobuf.Empty) returns (player.PauseState);
|
||||
*/
|
||||
togglePause(input: Empty, options?: RpcOptions): UnaryCall<Empty, PauseState>;
|
||||
/**
|
||||
* @generated from protobuf rpc: GetStatus(google.protobuf.Empty) returns (stream player.PlayerStatus);
|
||||
*/
|
||||
getStatus(input: Empty, options?: RpcOptions): ServerStreamingCall<Empty, PlayerStatus>;
|
||||
/**
|
||||
* @generated from protobuf rpc: SeekPosition(player.SeekPositionRequest) returns (player.SeekPositionResponse);
|
||||
*/
|
||||
seekPosition(
|
||||
input: SeekPositionRequest,
|
||||
options?: RpcOptions
|
||||
): UnaryCall<SeekPositionRequest, SeekPositionResponse>;
|
||||
/**
|
||||
* @generated from protobuf rpc: SetVolume(player.SetVolumeRequest) returns (player.SetVolumeResponse);
|
||||
*/
|
||||
setVolume(
|
||||
input: SetVolumeRequest,
|
||||
options?: RpcOptions
|
||||
): UnaryCall<SetVolumeRequest, SetVolumeResponse>;
|
||||
}
|
||||
/**
|
||||
* @generated from protobuf service player.Player
|
||||
@@ -56,19 +85,75 @@ export class PlayerClient implements IPlayerClient, ServiceInfo {
|
||||
);
|
||||
}
|
||||
/**
|
||||
* @generated from protobuf rpc: ResumeTrack(google.protobuf.Empty) returns (google.protobuf.Empty);
|
||||
* @generated from protobuf rpc: ResumeTrack(google.protobuf.Empty) returns (player.PauseState);
|
||||
*/
|
||||
resumeTrack(input: Empty, options?: RpcOptions): UnaryCall<Empty, Empty> {
|
||||
resumeTrack(input: Empty, options?: RpcOptions): UnaryCall<Empty, PauseState> {
|
||||
const method = this.methods[1],
|
||||
opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<Empty, Empty>('unary', this._transport, method, opt, input);
|
||||
return stackIntercept<Empty, PauseState>('unary', this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
* @generated from protobuf rpc: PauseTrack(google.protobuf.Empty) returns (google.protobuf.Empty);
|
||||
* @generated from protobuf rpc: PauseTrack(google.protobuf.Empty) returns (player.PauseState);
|
||||
*/
|
||||
pauseTrack(input: Empty, options?: RpcOptions): UnaryCall<Empty, Empty> {
|
||||
pauseTrack(input: Empty, options?: RpcOptions): UnaryCall<Empty, PauseState> {
|
||||
const method = this.methods[2],
|
||||
opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<Empty, Empty>('unary', this._transport, method, opt, input);
|
||||
return stackIntercept<Empty, PauseState>('unary', this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
* @generated from protobuf rpc: TogglePause(google.protobuf.Empty) returns (player.PauseState);
|
||||
*/
|
||||
togglePause(input: Empty, options?: RpcOptions): UnaryCall<Empty, PauseState> {
|
||||
const method = this.methods[3],
|
||||
opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<Empty, PauseState>('unary', this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
* @generated from protobuf rpc: GetStatus(google.protobuf.Empty) returns (stream player.PlayerStatus);
|
||||
*/
|
||||
getStatus(input: Empty, options?: RpcOptions): ServerStreamingCall<Empty, PlayerStatus> {
|
||||
const method = this.methods[4],
|
||||
opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<Empty, PlayerStatus>(
|
||||
'serverStreaming',
|
||||
this._transport,
|
||||
method,
|
||||
opt,
|
||||
input
|
||||
);
|
||||
}
|
||||
/**
|
||||
* @generated from protobuf rpc: SeekPosition(player.SeekPositionRequest) returns (player.SeekPositionResponse);
|
||||
*/
|
||||
seekPosition(
|
||||
input: SeekPositionRequest,
|
||||
options?: RpcOptions
|
||||
): UnaryCall<SeekPositionRequest, SeekPositionResponse> {
|
||||
const method = this.methods[5],
|
||||
opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<SeekPositionRequest, SeekPositionResponse>(
|
||||
'unary',
|
||||
this._transport,
|
||||
method,
|
||||
opt,
|
||||
input
|
||||
);
|
||||
}
|
||||
/**
|
||||
* @generated from protobuf rpc: SetVolume(player.SetVolumeRequest) returns (player.SetVolumeResponse);
|
||||
*/
|
||||
setVolume(
|
||||
input: SetVolumeRequest,
|
||||
options?: RpcOptions
|
||||
): UnaryCall<SetVolumeRequest, SetVolumeResponse> {
|
||||
const method = this.methods[6],
|
||||
opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<SetVolumeRequest, SetVolumeResponse>(
|
||||
'unary',
|
||||
this._transport,
|
||||
method,
|
||||
opt,
|
||||
input
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import { UnknownFieldHandler } from '@protobuf-ts/runtime';
|
||||
import type { PartialMessage } from '@protobuf-ts/runtime';
|
||||
import { reflectionMergePartial } from '@protobuf-ts/runtime';
|
||||
import { MessageType } from '@protobuf-ts/runtime';
|
||||
import { Track } from './library';
|
||||
/**
|
||||
* @generated from protobuf message player.PlayTrackRequest
|
||||
*/
|
||||
@@ -24,7 +25,82 @@ export interface PlayTrackRequest {
|
||||
/**
|
||||
* @generated from protobuf message player.PlayTrackResponse
|
||||
*/
|
||||
export interface PlayTrackResponse {}
|
||||
export interface PlayTrackResponse {
|
||||
/**
|
||||
* @generated from protobuf field: library.Track track = 1;
|
||||
*/
|
||||
track?: Track;
|
||||
/**
|
||||
* @generated from protobuf field: uint64 position = 2;
|
||||
*/
|
||||
position: bigint;
|
||||
}
|
||||
/**
|
||||
* @generated from protobuf message player.PlayerStatus
|
||||
*/
|
||||
export interface PlayerStatus {
|
||||
/**
|
||||
* @generated from protobuf field: optional library.Track currently_playing = 1;
|
||||
*/
|
||||
currentlyPlaying?: Track;
|
||||
/**
|
||||
* @generated from protobuf field: bool is_paused = 2;
|
||||
*/
|
||||
isPaused: boolean;
|
||||
/**
|
||||
* @generated from protobuf field: float volume = 3;
|
||||
*/
|
||||
volume: number;
|
||||
/**
|
||||
* @generated from protobuf field: uint64 progress = 4;
|
||||
*/
|
||||
progress: bigint;
|
||||
}
|
||||
/**
|
||||
* @generated from protobuf message player.PauseState
|
||||
*/
|
||||
export interface PauseState {
|
||||
/**
|
||||
* @generated from protobuf field: bool is_paused = 1;
|
||||
*/
|
||||
isPaused: boolean;
|
||||
}
|
||||
/**
|
||||
* @generated from protobuf message player.SeekPositionRequest
|
||||
*/
|
||||
export interface SeekPositionRequest {
|
||||
/**
|
||||
* @generated from protobuf field: uint64 position = 1;
|
||||
*/
|
||||
position: bigint;
|
||||
}
|
||||
/**
|
||||
* @generated from protobuf message player.SeekPositionResponse
|
||||
*/
|
||||
export interface SeekPositionResponse {
|
||||
/**
|
||||
* @generated from protobuf field: uint64 position = 1;
|
||||
*/
|
||||
position: bigint;
|
||||
}
|
||||
/**
|
||||
* @generated from protobuf message player.SetVolumeRequest
|
||||
*/
|
||||
export interface SetVolumeRequest {
|
||||
/**
|
||||
* @generated from protobuf field: float volume = 1;
|
||||
*/
|
||||
volume: number;
|
||||
}
|
||||
/**
|
||||
* @generated from protobuf message player.SetVolumeResponse
|
||||
*/
|
||||
export interface SetVolumeResponse {
|
||||
/**
|
||||
* @generated from protobuf field: float volume = 1;
|
||||
*/
|
||||
volume: number;
|
||||
}
|
||||
// @generated message type with reflection information, may provide speed optimized methods
|
||||
class PlayTrackRequest$Type extends MessageType<PlayTrackRequest> {
|
||||
constructor() {
|
||||
@@ -90,10 +166,20 @@ export const PlayTrackRequest = new PlayTrackRequest$Type();
|
||||
// @generated message type with reflection information, may provide speed optimized methods
|
||||
class PlayTrackResponse$Type extends MessageType<PlayTrackResponse> {
|
||||
constructor() {
|
||||
super('player.PlayTrackResponse', []);
|
||||
super('player.PlayTrackResponse', [
|
||||
{ no: 1, name: 'track', kind: 'message', T: () => Track },
|
||||
{
|
||||
no: 2,
|
||||
name: 'position',
|
||||
kind: 'scalar',
|
||||
T: 4 /*ScalarType.UINT64*/,
|
||||
L: 0 /*LongType.BIGINT*/
|
||||
}
|
||||
]);
|
||||
}
|
||||
create(value?: PartialMessage<PlayTrackResponse>): PlayTrackResponse {
|
||||
const message = globalThis.Object.create(this.messagePrototype!);
|
||||
message.position = 0n;
|
||||
if (value !== undefined) reflectionMergePartial<PlayTrackResponse>(this, message, value);
|
||||
return message;
|
||||
}
|
||||
@@ -103,13 +189,50 @@ class PlayTrackResponse$Type extends MessageType<PlayTrackResponse> {
|
||||
options: BinaryReadOptions,
|
||||
target?: PlayTrackResponse
|
||||
): PlayTrackResponse {
|
||||
return target ?? this.create();
|
||||
let message = target ?? this.create(),
|
||||
end = reader.pos + length;
|
||||
while (reader.pos < end) {
|
||||
let [fieldNo, wireType] = reader.tag();
|
||||
switch (fieldNo) {
|
||||
case /* library.Track track */ 1:
|
||||
message.track = Track.internalBinaryRead(reader, reader.uint32(), options, message.track);
|
||||
break;
|
||||
case /* uint64 position */ 2:
|
||||
message.position = reader.uint64().toBigInt();
|
||||
break;
|
||||
default:
|
||||
let u = options.readUnknownField;
|
||||
if (u === 'throw')
|
||||
throw new globalThis.Error(
|
||||
`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`
|
||||
);
|
||||
let d = reader.skip(wireType);
|
||||
if (u !== false)
|
||||
(u === true ? UnknownFieldHandler.onRead : u)(
|
||||
this.typeName,
|
||||
message,
|
||||
fieldNo,
|
||||
wireType,
|
||||
d
|
||||
);
|
||||
}
|
||||
}
|
||||
return message;
|
||||
}
|
||||
internalBinaryWrite(
|
||||
message: PlayTrackResponse,
|
||||
writer: IBinaryWriter,
|
||||
options: BinaryWriteOptions
|
||||
): IBinaryWriter {
|
||||
/* library.Track track = 1; */
|
||||
if (message.track)
|
||||
Track.internalBinaryWrite(
|
||||
message.track,
|
||||
writer.tag(1, WireType.LengthDelimited).fork(),
|
||||
options
|
||||
).join();
|
||||
/* uint64 position = 2; */
|
||||
if (message.position !== 0n) writer.tag(2, WireType.Varint).uint64(message.position);
|
||||
let u = options.writeUnknownFields;
|
||||
if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||
return writer;
|
||||
@@ -119,11 +242,435 @@ class PlayTrackResponse$Type extends MessageType<PlayTrackResponse> {
|
||||
* @generated MessageType for protobuf message player.PlayTrackResponse
|
||||
*/
|
||||
export const PlayTrackResponse = new PlayTrackResponse$Type();
|
||||
// @generated message type with reflection information, may provide speed optimized methods
|
||||
class PlayerStatus$Type extends MessageType<PlayerStatus> {
|
||||
constructor() {
|
||||
super('player.PlayerStatus', [
|
||||
{ no: 1, name: 'currently_playing', kind: 'message', T: () => Track },
|
||||
{ no: 2, name: 'is_paused', kind: 'scalar', T: 8 /*ScalarType.BOOL*/ },
|
||||
{ no: 3, name: 'volume', kind: 'scalar', T: 2 /*ScalarType.FLOAT*/ },
|
||||
{
|
||||
no: 4,
|
||||
name: 'progress',
|
||||
kind: 'scalar',
|
||||
T: 4 /*ScalarType.UINT64*/,
|
||||
L: 0 /*LongType.BIGINT*/
|
||||
}
|
||||
]);
|
||||
}
|
||||
create(value?: PartialMessage<PlayerStatus>): PlayerStatus {
|
||||
const message = globalThis.Object.create(this.messagePrototype!);
|
||||
message.isPaused = false;
|
||||
message.volume = 0;
|
||||
message.progress = 0n;
|
||||
if (value !== undefined) reflectionMergePartial<PlayerStatus>(this, message, value);
|
||||
return message;
|
||||
}
|
||||
internalBinaryRead(
|
||||
reader: IBinaryReader,
|
||||
length: number,
|
||||
options: BinaryReadOptions,
|
||||
target?: PlayerStatus
|
||||
): PlayerStatus {
|
||||
let message = target ?? this.create(),
|
||||
end = reader.pos + length;
|
||||
while (reader.pos < end) {
|
||||
let [fieldNo, wireType] = reader.tag();
|
||||
switch (fieldNo) {
|
||||
case /* optional library.Track currently_playing */ 1:
|
||||
message.currentlyPlaying = Track.internalBinaryRead(
|
||||
reader,
|
||||
reader.uint32(),
|
||||
options,
|
||||
message.currentlyPlaying
|
||||
);
|
||||
break;
|
||||
case /* bool is_paused */ 2:
|
||||
message.isPaused = reader.bool();
|
||||
break;
|
||||
case /* float volume */ 3:
|
||||
message.volume = reader.float();
|
||||
break;
|
||||
case /* uint64 progress */ 4:
|
||||
message.progress = reader.uint64().toBigInt();
|
||||
break;
|
||||
default:
|
||||
let u = options.readUnknownField;
|
||||
if (u === 'throw')
|
||||
throw new globalThis.Error(
|
||||
`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`
|
||||
);
|
||||
let d = reader.skip(wireType);
|
||||
if (u !== false)
|
||||
(u === true ? UnknownFieldHandler.onRead : u)(
|
||||
this.typeName,
|
||||
message,
|
||||
fieldNo,
|
||||
wireType,
|
||||
d
|
||||
);
|
||||
}
|
||||
}
|
||||
return message;
|
||||
}
|
||||
internalBinaryWrite(
|
||||
message: PlayerStatus,
|
||||
writer: IBinaryWriter,
|
||||
options: BinaryWriteOptions
|
||||
): IBinaryWriter {
|
||||
/* optional library.Track currently_playing = 1; */
|
||||
if (message.currentlyPlaying)
|
||||
Track.internalBinaryWrite(
|
||||
message.currentlyPlaying,
|
||||
writer.tag(1, WireType.LengthDelimited).fork(),
|
||||
options
|
||||
).join();
|
||||
/* bool is_paused = 2; */
|
||||
if (message.isPaused !== false) writer.tag(2, WireType.Varint).bool(message.isPaused);
|
||||
/* float volume = 3; */
|
||||
if (message.volume !== 0) writer.tag(3, WireType.Bit32).float(message.volume);
|
||||
/* uint64 progress = 4; */
|
||||
if (message.progress !== 0n) writer.tag(4, WireType.Varint).uint64(message.progress);
|
||||
let u = options.writeUnknownFields;
|
||||
if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||
return writer;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @generated MessageType for protobuf message player.PlayerStatus
|
||||
*/
|
||||
export const PlayerStatus = new PlayerStatus$Type();
|
||||
// @generated message type with reflection information, may provide speed optimized methods
|
||||
class PauseState$Type extends MessageType<PauseState> {
|
||||
constructor() {
|
||||
super('player.PauseState', [
|
||||
{ no: 1, name: 'is_paused', kind: 'scalar', T: 8 /*ScalarType.BOOL*/ }
|
||||
]);
|
||||
}
|
||||
create(value?: PartialMessage<PauseState>): PauseState {
|
||||
const message = globalThis.Object.create(this.messagePrototype!);
|
||||
message.isPaused = false;
|
||||
if (value !== undefined) reflectionMergePartial<PauseState>(this, message, value);
|
||||
return message;
|
||||
}
|
||||
internalBinaryRead(
|
||||
reader: IBinaryReader,
|
||||
length: number,
|
||||
options: BinaryReadOptions,
|
||||
target?: PauseState
|
||||
): PauseState {
|
||||
let message = target ?? this.create(),
|
||||
end = reader.pos + length;
|
||||
while (reader.pos < end) {
|
||||
let [fieldNo, wireType] = reader.tag();
|
||||
switch (fieldNo) {
|
||||
case /* bool is_paused */ 1:
|
||||
message.isPaused = reader.bool();
|
||||
break;
|
||||
default:
|
||||
let u = options.readUnknownField;
|
||||
if (u === 'throw')
|
||||
throw new globalThis.Error(
|
||||
`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`
|
||||
);
|
||||
let d = reader.skip(wireType);
|
||||
if (u !== false)
|
||||
(u === true ? UnknownFieldHandler.onRead : u)(
|
||||
this.typeName,
|
||||
message,
|
||||
fieldNo,
|
||||
wireType,
|
||||
d
|
||||
);
|
||||
}
|
||||
}
|
||||
return message;
|
||||
}
|
||||
internalBinaryWrite(
|
||||
message: PauseState,
|
||||
writer: IBinaryWriter,
|
||||
options: BinaryWriteOptions
|
||||
): IBinaryWriter {
|
||||
/* bool is_paused = 1; */
|
||||
if (message.isPaused !== false) writer.tag(1, WireType.Varint).bool(message.isPaused);
|
||||
let u = options.writeUnknownFields;
|
||||
if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||
return writer;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @generated MessageType for protobuf message player.PauseState
|
||||
*/
|
||||
export const PauseState = new PauseState$Type();
|
||||
// @generated message type with reflection information, may provide speed optimized methods
|
||||
class SeekPositionRequest$Type extends MessageType<SeekPositionRequest> {
|
||||
constructor() {
|
||||
super('player.SeekPositionRequest', [
|
||||
{
|
||||
no: 1,
|
||||
name: 'position',
|
||||
kind: 'scalar',
|
||||
T: 4 /*ScalarType.UINT64*/,
|
||||
L: 0 /*LongType.BIGINT*/
|
||||
}
|
||||
]);
|
||||
}
|
||||
create(value?: PartialMessage<SeekPositionRequest>): SeekPositionRequest {
|
||||
const message = globalThis.Object.create(this.messagePrototype!);
|
||||
message.position = 0n;
|
||||
if (value !== undefined) reflectionMergePartial<SeekPositionRequest>(this, message, value);
|
||||
return message;
|
||||
}
|
||||
internalBinaryRead(
|
||||
reader: IBinaryReader,
|
||||
length: number,
|
||||
options: BinaryReadOptions,
|
||||
target?: SeekPositionRequest
|
||||
): SeekPositionRequest {
|
||||
let message = target ?? this.create(),
|
||||
end = reader.pos + length;
|
||||
while (reader.pos < end) {
|
||||
let [fieldNo, wireType] = reader.tag();
|
||||
switch (fieldNo) {
|
||||
case /* uint64 position */ 1:
|
||||
message.position = reader.uint64().toBigInt();
|
||||
break;
|
||||
default:
|
||||
let u = options.readUnknownField;
|
||||
if (u === 'throw')
|
||||
throw new globalThis.Error(
|
||||
`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`
|
||||
);
|
||||
let d = reader.skip(wireType);
|
||||
if (u !== false)
|
||||
(u === true ? UnknownFieldHandler.onRead : u)(
|
||||
this.typeName,
|
||||
message,
|
||||
fieldNo,
|
||||
wireType,
|
||||
d
|
||||
);
|
||||
}
|
||||
}
|
||||
return message;
|
||||
}
|
||||
internalBinaryWrite(
|
||||
message: SeekPositionRequest,
|
||||
writer: IBinaryWriter,
|
||||
options: BinaryWriteOptions
|
||||
): IBinaryWriter {
|
||||
/* uint64 position = 1; */
|
||||
if (message.position !== 0n) writer.tag(1, WireType.Varint).uint64(message.position);
|
||||
let u = options.writeUnknownFields;
|
||||
if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||
return writer;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @generated MessageType for protobuf message player.SeekPositionRequest
|
||||
*/
|
||||
export const SeekPositionRequest = new SeekPositionRequest$Type();
|
||||
// @generated message type with reflection information, may provide speed optimized methods
|
||||
class SeekPositionResponse$Type extends MessageType<SeekPositionResponse> {
|
||||
constructor() {
|
||||
super('player.SeekPositionResponse', [
|
||||
{
|
||||
no: 1,
|
||||
name: 'position',
|
||||
kind: 'scalar',
|
||||
T: 4 /*ScalarType.UINT64*/,
|
||||
L: 0 /*LongType.BIGINT*/
|
||||
}
|
||||
]);
|
||||
}
|
||||
create(value?: PartialMessage<SeekPositionResponse>): SeekPositionResponse {
|
||||
const message = globalThis.Object.create(this.messagePrototype!);
|
||||
message.position = 0n;
|
||||
if (value !== undefined) reflectionMergePartial<SeekPositionResponse>(this, message, value);
|
||||
return message;
|
||||
}
|
||||
internalBinaryRead(
|
||||
reader: IBinaryReader,
|
||||
length: number,
|
||||
options: BinaryReadOptions,
|
||||
target?: SeekPositionResponse
|
||||
): SeekPositionResponse {
|
||||
let message = target ?? this.create(),
|
||||
end = reader.pos + length;
|
||||
while (reader.pos < end) {
|
||||
let [fieldNo, wireType] = reader.tag();
|
||||
switch (fieldNo) {
|
||||
case /* uint64 position */ 1:
|
||||
message.position = reader.uint64().toBigInt();
|
||||
break;
|
||||
default:
|
||||
let u = options.readUnknownField;
|
||||
if (u === 'throw')
|
||||
throw new globalThis.Error(
|
||||
`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`
|
||||
);
|
||||
let d = reader.skip(wireType);
|
||||
if (u !== false)
|
||||
(u === true ? UnknownFieldHandler.onRead : u)(
|
||||
this.typeName,
|
||||
message,
|
||||
fieldNo,
|
||||
wireType,
|
||||
d
|
||||
);
|
||||
}
|
||||
}
|
||||
return message;
|
||||
}
|
||||
internalBinaryWrite(
|
||||
message: SeekPositionResponse,
|
||||
writer: IBinaryWriter,
|
||||
options: BinaryWriteOptions
|
||||
): IBinaryWriter {
|
||||
/* uint64 position = 1; */
|
||||
if (message.position !== 0n) writer.tag(1, WireType.Varint).uint64(message.position);
|
||||
let u = options.writeUnknownFields;
|
||||
if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||
return writer;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @generated MessageType for protobuf message player.SeekPositionResponse
|
||||
*/
|
||||
export const SeekPositionResponse = new SeekPositionResponse$Type();
|
||||
// @generated message type with reflection information, may provide speed optimized methods
|
||||
class SetVolumeRequest$Type extends MessageType<SetVolumeRequest> {
|
||||
constructor() {
|
||||
super('player.SetVolumeRequest', [
|
||||
{ no: 1, name: 'volume', kind: 'scalar', T: 2 /*ScalarType.FLOAT*/ }
|
||||
]);
|
||||
}
|
||||
create(value?: PartialMessage<SetVolumeRequest>): SetVolumeRequest {
|
||||
const message = globalThis.Object.create(this.messagePrototype!);
|
||||
message.volume = 0;
|
||||
if (value !== undefined) reflectionMergePartial<SetVolumeRequest>(this, message, value);
|
||||
return message;
|
||||
}
|
||||
internalBinaryRead(
|
||||
reader: IBinaryReader,
|
||||
length: number,
|
||||
options: BinaryReadOptions,
|
||||
target?: SetVolumeRequest
|
||||
): SetVolumeRequest {
|
||||
let message = target ?? this.create(),
|
||||
end = reader.pos + length;
|
||||
while (reader.pos < end) {
|
||||
let [fieldNo, wireType] = reader.tag();
|
||||
switch (fieldNo) {
|
||||
case /* float volume */ 1:
|
||||
message.volume = reader.float();
|
||||
break;
|
||||
default:
|
||||
let u = options.readUnknownField;
|
||||
if (u === 'throw')
|
||||
throw new globalThis.Error(
|
||||
`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`
|
||||
);
|
||||
let d = reader.skip(wireType);
|
||||
if (u !== false)
|
||||
(u === true ? UnknownFieldHandler.onRead : u)(
|
||||
this.typeName,
|
||||
message,
|
||||
fieldNo,
|
||||
wireType,
|
||||
d
|
||||
);
|
||||
}
|
||||
}
|
||||
return message;
|
||||
}
|
||||
internalBinaryWrite(
|
||||
message: SetVolumeRequest,
|
||||
writer: IBinaryWriter,
|
||||
options: BinaryWriteOptions
|
||||
): IBinaryWriter {
|
||||
/* float volume = 1; */
|
||||
if (message.volume !== 0) writer.tag(1, WireType.Bit32).float(message.volume);
|
||||
let u = options.writeUnknownFields;
|
||||
if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||
return writer;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @generated MessageType for protobuf message player.SetVolumeRequest
|
||||
*/
|
||||
export const SetVolumeRequest = new SetVolumeRequest$Type();
|
||||
// @generated message type with reflection information, may provide speed optimized methods
|
||||
class SetVolumeResponse$Type extends MessageType<SetVolumeResponse> {
|
||||
constructor() {
|
||||
super('player.SetVolumeResponse', [
|
||||
{ no: 1, name: 'volume', kind: 'scalar', T: 2 /*ScalarType.FLOAT*/ }
|
||||
]);
|
||||
}
|
||||
create(value?: PartialMessage<SetVolumeResponse>): SetVolumeResponse {
|
||||
const message = globalThis.Object.create(this.messagePrototype!);
|
||||
message.volume = 0;
|
||||
if (value !== undefined) reflectionMergePartial<SetVolumeResponse>(this, message, value);
|
||||
return message;
|
||||
}
|
||||
internalBinaryRead(
|
||||
reader: IBinaryReader,
|
||||
length: number,
|
||||
options: BinaryReadOptions,
|
||||
target?: SetVolumeResponse
|
||||
): SetVolumeResponse {
|
||||
let message = target ?? this.create(),
|
||||
end = reader.pos + length;
|
||||
while (reader.pos < end) {
|
||||
let [fieldNo, wireType] = reader.tag();
|
||||
switch (fieldNo) {
|
||||
case /* float volume */ 1:
|
||||
message.volume = reader.float();
|
||||
break;
|
||||
default:
|
||||
let u = options.readUnknownField;
|
||||
if (u === 'throw')
|
||||
throw new globalThis.Error(
|
||||
`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`
|
||||
);
|
||||
let d = reader.skip(wireType);
|
||||
if (u !== false)
|
||||
(u === true ? UnknownFieldHandler.onRead : u)(
|
||||
this.typeName,
|
||||
message,
|
||||
fieldNo,
|
||||
wireType,
|
||||
d
|
||||
);
|
||||
}
|
||||
}
|
||||
return message;
|
||||
}
|
||||
internalBinaryWrite(
|
||||
message: SetVolumeResponse,
|
||||
writer: IBinaryWriter,
|
||||
options: BinaryWriteOptions
|
||||
): IBinaryWriter {
|
||||
/* float volume = 1; */
|
||||
if (message.volume !== 0) writer.tag(1, WireType.Bit32).float(message.volume);
|
||||
let u = options.writeUnknownFields;
|
||||
if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||
return writer;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @generated MessageType for protobuf message player.SetVolumeResponse
|
||||
*/
|
||||
export const SetVolumeResponse = new SetVolumeResponse$Type();
|
||||
/**
|
||||
* @generated ServiceType for protobuf service player.Player
|
||||
*/
|
||||
export const Player = new ServiceType('player.Player', [
|
||||
{ name: 'PlayTrack', options: {}, I: PlayTrackRequest, O: PlayTrackResponse },
|
||||
{ name: 'ResumeTrack', options: {}, I: Empty, O: Empty },
|
||||
{ name: 'PauseTrack', options: {}, I: Empty, O: Empty }
|
||||
{ name: 'ResumeTrack', options: {}, I: Empty, O: PauseState },
|
||||
{ name: 'PauseTrack', options: {}, I: Empty, O: PauseState },
|
||||
{ name: 'TogglePause', options: {}, I: Empty, O: PauseState },
|
||||
{ name: 'GetStatus', serverStreaming: true, options: {}, I: Empty, O: PlayerStatus },
|
||||
{ name: 'SeekPosition', options: {}, I: SeekPositionRequest, O: SeekPositionResponse },
|
||||
{ name: 'SetVolume', options: {}, I: SetVolumeRequest, O: SetVolumeResponse }
|
||||
]);
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export const currentlyPlaying = writable<string | null>(null);
|
||||
export const volume = writable<number>(1.0);
|
||||
@@ -5,8 +5,17 @@
|
||||
import AppSidebar from '$lib/components/groove/AppSidebar.svelte';
|
||||
import Footer from '$lib/components/groove/Footer.svelte';
|
||||
import { ScrollArea } from '$lib/components/ui/scroll-area';
|
||||
import { getPlayerState, setPlayerState } from '$lib/player.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
let { children } = $props();
|
||||
|
||||
setPlayerState();
|
||||
const player = getPlayerState();
|
||||
|
||||
onMount(() => {
|
||||
return () => player.abort();
|
||||
});
|
||||
</script>
|
||||
|
||||
<ModeWatcher />
|
||||
|
||||
1
src/routes/+layout.ts
Normal file
1
src/routes/+layout.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const ssr = false;
|
||||
@@ -1,4 +1,5 @@
|
||||
import { PlayerClient } from '$lib/proto/player.client';
|
||||
import { fail } from '@sveltejs/kit';
|
||||
import { protoTransport } from '../../hooks.server';
|
||||
import type { Actions } from './$types';
|
||||
|
||||
@@ -6,11 +7,55 @@ export const actions = {
|
||||
resume: async () => {
|
||||
const client = new PlayerClient(protoTransport);
|
||||
|
||||
await client.resumeTrack({});
|
||||
const response = await client.resumeTrack({});
|
||||
|
||||
return {
|
||||
isPaused: response.response.isPaused
|
||||
};
|
||||
},
|
||||
pause: async () => {
|
||||
const client = new PlayerClient(protoTransport);
|
||||
|
||||
await client.pauseTrack({});
|
||||
const response = await client.pauseTrack({});
|
||||
|
||||
return {
|
||||
isPaused: response.response.isPaused
|
||||
};
|
||||
},
|
||||
seek: async ({ request }) => {
|
||||
const formData = await request.formData();
|
||||
|
||||
const position = formData.get('position')?.toString();
|
||||
|
||||
if (!position) {
|
||||
return fail(400);
|
||||
}
|
||||
|
||||
const client = new PlayerClient(protoTransport);
|
||||
|
||||
const response = await client.seekPosition({ position: BigInt(position) });
|
||||
|
||||
return {
|
||||
position: response.response.position
|
||||
};
|
||||
},
|
||||
volume: async ({ request }) => {
|
||||
const formData = await request.formData();
|
||||
|
||||
const volume = formData.get('volume')?.toString();
|
||||
|
||||
if (!volume) {
|
||||
return fail(400);
|
||||
}
|
||||
|
||||
const client = new PlayerClient(protoTransport);
|
||||
|
||||
const response = await client.setVolume({
|
||||
volume: parseFloat(volume)
|
||||
});
|
||||
|
||||
return {
|
||||
volume: response.response.volume
|
||||
};
|
||||
}
|
||||
} satisfies Actions;
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
import { PlayerClient } from '$lib/proto/player.client';
|
||||
import { fail } from '@sveltejs/kit';
|
||||
import { protoTransport } from '../../../hooks.server';
|
||||
import type { Actions } from './$types';
|
||||
import { serializable } from '$lib/proto';
|
||||
import type { PlayTrackResponse } from '$lib/proto/player';
|
||||
|
||||
export const actions = {
|
||||
play: async ({ params }) => {
|
||||
const client = new PlayerClient(protoTransport);
|
||||
|
||||
await client.playTrack({
|
||||
const response = await client.playTrack({
|
||||
hash: params.hash
|
||||
});
|
||||
|
||||
if (response.response.track === undefined) {
|
||||
return fail(500);
|
||||
}
|
||||
|
||||
return serializable<PlayTrackResponse>(response.response);
|
||||
}
|
||||
} satisfies Actions;
|
||||
|
||||
Reference in New Issue
Block a user