forked from Mastodon/mastoradio-la-radio-di-mastodon
simplify store, add more feats
This commit is contained in:
parent
cd10193d3e
commit
ae187a96ef
|
@ -3018,11 +3018,6 @@
|
|||
"locate-path": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"folktale": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/folktale/-/folktale-2.3.2.tgz",
|
||||
"integrity": "sha512-+8GbtQBwEqutP0v3uajDDoN64K2ehmHd0cjlghhxh0WpcfPzAIjPA03e1VvHlxL02FVGR0A6lwXsNQKn3H1RNQ=="
|
||||
},
|
||||
"for-in": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
"dependencies": {
|
||||
"@babel/runtime-corejs3": "^7.7.7",
|
||||
"date-fns": "^2.9.0",
|
||||
"folktale": "^2.3.2",
|
||||
"get-urls": "^9.2.0",
|
||||
"iter-tools": "^7.0.0-rc.0",
|
||||
"yt-player": "^3.4.3"
|
||||
|
|
|
@ -4,7 +4,9 @@
|
|||
</header>
|
||||
|
||||
<section class="viewer">
|
||||
<Viewer></Viewer>
|
||||
{#if $current}
|
||||
<Viewer></Viewer>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<section class="queue">
|
||||
|
@ -23,6 +25,8 @@
|
|||
import Controls from '/components/Controls.svelte'
|
||||
import Queue from '/components/Queue.svelte'
|
||||
import Viewer from '/components/Viewer.svelte'
|
||||
|
||||
import { current } from '/store.js'
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -3,14 +3,12 @@
|
|||
<button on:click={() => $muted = !$muted}>
|
||||
{#if $muted}
|
||||
🔇
|
||||
{:else}
|
||||
{#if $volume < 20}
|
||||
🔈
|
||||
{:else if $volume < 80}
|
||||
🔉
|
||||
{:else}
|
||||
🔊
|
||||
{/if}
|
||||
{:else if $volume < 20}
|
||||
🔈
|
||||
{:else if $volume < 80}
|
||||
🔉
|
||||
{:else }
|
||||
🔊
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
|
@ -18,9 +16,21 @@
|
|||
</div>
|
||||
|
||||
<div class="controls-group">
|
||||
<button on:click={() => entry.previous()}>⏮️</button>
|
||||
<button on:click={() => $paused = !$paused}>{#if $paused}▶️{:else}⏸️{/if}</button>
|
||||
<button on:click={() => entry.next()}>⏭️</button>
|
||||
<button class:cant={!$canPrevious} on:click={() => selectPrevious()}>⏮️</button>
|
||||
|
||||
<button on:click={() => $paused = !$paused}>
|
||||
{#if $index === null}
|
||||
▶️
|
||||
{:else if $loading}
|
||||
🕒
|
||||
{:else if $paused}
|
||||
▶️
|
||||
{:else }
|
||||
⏸️
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
<button class:cant={!$canNext} on:click={() => selectNext()}>⏭️</button>
|
||||
</div>
|
||||
|
||||
<div class="controls-group">
|
||||
|
@ -30,7 +40,18 @@
|
|||
</div>
|
||||
|
||||
<script>
|
||||
import { paused, volume, muted, entry } from '/stores.js'
|
||||
import {
|
||||
paused,
|
||||
muted,
|
||||
volume,
|
||||
index,
|
||||
queue,
|
||||
canPrevious,
|
||||
canNext,
|
||||
selectPrevious,
|
||||
selectNext,
|
||||
loading
|
||||
} from '/store.js'
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
@ -42,4 +63,8 @@
|
|||
.controls-group {
|
||||
margin: 0 1rem;
|
||||
}
|
||||
|
||||
.cant {
|
||||
background-color: red;
|
||||
}
|
||||
</style>
|
|
@ -1,53 +1,58 @@
|
|||
<div>
|
||||
{#each $entries as entry}
|
||||
<div class="entry" class:active={entry === $currentEntry}>
|
||||
{#each $queue as track, i}
|
||||
<div class="entry" class:active={i === $index}>
|
||||
<div>
|
||||
<button on:click={() => toggleEntry(entry)}>
|
||||
{#if entry === $currentEntry && !$paused}
|
||||
⏸️
|
||||
{:else}
|
||||
<button on:click={() => toggle(i)}>
|
||||
{#if i != $index}
|
||||
▶️
|
||||
{:else if $loading}
|
||||
🕒
|
||||
{:else if $paused}
|
||||
▶️
|
||||
{:else}
|
||||
⏸️
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>{entry.metadata.title}</div>
|
||||
<div>{track.metadata.title}</div>
|
||||
|
||||
<div>
|
||||
<b>{entry.status.account.username} <small style="color: dimgray">{entry.status.account.acct}</small></b>
|
||||
{entry.data.url}
|
||||
<b>{track.status.account.username} <small style="color: dimgray">{track.status.account.acct}</small></b>
|
||||
{track.data.url}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
<button on:click={() => entries.load(5)}>LOAD 5 MOAR</button>
|
||||
{#if $enqueueing}
|
||||
LOADING ...
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
import { onMount } from 'svelte'
|
||||
import { paused, entry as currentEntry, entries } from '/stores.js'
|
||||
import { index, queue, paused, enqueueing, enqueue, loading } from '/store.js'
|
||||
|
||||
const toggleEntry = (entry) => {
|
||||
if (entry !== $currentEntry) {
|
||||
$currentEntry = entry
|
||||
} else {
|
||||
const toggle = i => {
|
||||
if (i === $index) {
|
||||
$paused = !$paused
|
||||
} else {
|
||||
$index = i
|
||||
$paused = false
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
const unsubscribe = entries.subscribe(async (xs) => {
|
||||
if (xs.length) {
|
||||
const [firstEntry] = xs
|
||||
currentEntry.set(firstEntry)
|
||||
unsubscribe()
|
||||
}
|
||||
enqueue().then(() => {
|
||||
$index = 0
|
||||
})
|
||||
|
||||
entries.load(1)
|
||||
})
|
||||
|
||||
$: if ($index !== null && $index === $queue.length - 1) {
|
||||
enqueue()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -1,12 +1,22 @@
|
|||
<div>
|
||||
<div class:hidden={!duration}>
|
||||
<div class="embed-container" class:hidden={!duration}>
|
||||
<div bind:this={element}></div>
|
||||
<div class="embed-overlay" on:click={() => $paused = !$paused}></div>
|
||||
</div>
|
||||
|
||||
{#if duration}
|
||||
{currentTimeText}
|
||||
<input type="range" min="0" max={duration} value={currentTime} disabled>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max={duration}
|
||||
value={currentTime}
|
||||
on:input={ (e) => updatePlayerCurrentTime(e.target.value) }
|
||||
on:mousedown={() => { if (player && !$paused) player.pause() }}
|
||||
on:mouseup={() => { if (player && !$paused) player.play() }}>
|
||||
{durationText}
|
||||
{:else}
|
||||
LOADING TRACK
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
@ -14,8 +24,8 @@
|
|||
import { onMount, onDestroy } from 'svelte'
|
||||
import { get } from 'svelte/store'
|
||||
import YoutubePlayer from 'yt-player'
|
||||
import { entry, paused, muted, volume } from '/stores.js'
|
||||
import { secondsToElapsedTime } from '/util.js'
|
||||
import { paused, muted, volume, current, selectNext, loading } from '/store.js'
|
||||
|
||||
let element
|
||||
let player
|
||||
|
@ -29,40 +39,45 @@
|
|||
$: currentTimeText = currentTime !== null ? secondsToElapsedTime(currentTime) : null
|
||||
$: durationText = duration !== null ? secondsToElapsedTime(duration) : null
|
||||
|
||||
$: updateEntry($entry)
|
||||
$: updatePaused($paused)
|
||||
$: updateMuted($muted)
|
||||
$: updateVolume($volume)
|
||||
$: updatePlayerVideoId($current)
|
||||
$: updatePlayerPaused($paused)
|
||||
$: updatePlayerMuted($muted)
|
||||
$: updatePlayerVolume($volume)
|
||||
|
||||
const updateViewerDurationCallback = () => {
|
||||
if (player) {
|
||||
duration = player.getDuration()
|
||||
currentTime = player.getCurrentTime()
|
||||
$loading = false
|
||||
}
|
||||
}
|
||||
|
||||
const updateEntry = (entry) => {
|
||||
if (player && entry) {
|
||||
const updatePlayerVideoId = ($current) => {
|
||||
if (player && $current) {
|
||||
duration = null
|
||||
currentTime = null
|
||||
$loading = true
|
||||
player.off('playing', updateViewerDurationCallback)
|
||||
|
||||
player.load(entry.data.id, !$paused)
|
||||
player.load($current.data.id, !$paused)
|
||||
}
|
||||
}
|
||||
|
||||
const updatePaused = (paused) => {
|
||||
const updatePlayerPaused = (paused) => {
|
||||
if (player) paused ? player.pause() : player.play()
|
||||
}
|
||||
|
||||
const updateMuted = (muted) => {
|
||||
const updatePlayerMuted = (muted) => {
|
||||
if (player) muted ? player.mute() : player.unMute()
|
||||
}
|
||||
|
||||
const updateVolume = (volume) => {
|
||||
const updatePlayerVolume = (volume) => {
|
||||
if (player) player.setVolume(volume)
|
||||
}
|
||||
|
||||
const updatePlayerCurrentTime = (seconds) => {
|
||||
if (player) player.seek(seconds)
|
||||
}
|
||||
|
||||
|
||||
onMount(() => {
|
||||
|
@ -77,17 +92,9 @@
|
|||
related: false
|
||||
})
|
||||
|
||||
updatePaused($paused)
|
||||
updateMuted($muted)
|
||||
updateVolume($volume)
|
||||
|
||||
// player.on('playing', () => {
|
||||
// $paused = false
|
||||
// })
|
||||
|
||||
// player.on('paused', () => {
|
||||
// $paused = true
|
||||
// })
|
||||
updatePlayerPaused($paused)
|
||||
updatePlayerMuted($muted)
|
||||
updatePlayerVolume($volume)
|
||||
|
||||
player.on('unstarted', () => {
|
||||
player.once('playing', updateViewerDurationCallback)
|
||||
|
@ -97,16 +104,18 @@
|
|||
currentTime = time
|
||||
})
|
||||
|
||||
player.on('ended', () => entry.next())
|
||||
player.on('ended', () => {
|
||||
selectNext()
|
||||
})
|
||||
|
||||
player.on('unplayable', (...args) => {
|
||||
console.log('unplayable', ...args)
|
||||
entry.next()
|
||||
selectNext()
|
||||
})
|
||||
|
||||
player.on('error', (...args) => {
|
||||
console.log('error', ...args)
|
||||
entry.next()
|
||||
selectNext()
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -121,4 +130,16 @@
|
|||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.embed-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.embed-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
</style>
|
|
@ -1,122 +0,0 @@
|
|||
import { writable, get } from 'svelte/store'
|
||||
import { mkTracksIterator } from '/util.js'
|
||||
|
||||
export const writableLocalStorage = (key, value) => {
|
||||
const item = JSON.parse(localStorage.getItem(key))
|
||||
const store = writable(item === null ? value : item)
|
||||
|
||||
store.subscribe(x => localStorage.setItem(key, JSON.stringify(x)))
|
||||
|
||||
return store
|
||||
}
|
||||
|
||||
export const stackStore = (domain, hashtags) => {
|
||||
const tracksIterator = mkTracksIterator(domain, hashtags)
|
||||
|
||||
const store = writable([])
|
||||
const { update, subscribe } = store
|
||||
|
||||
let promise = Promise.resolve()
|
||||
const buffer = []
|
||||
|
||||
const load = async () => {
|
||||
|
||||
|
||||
const n = 5 - buffer.length
|
||||
|
||||
for (let i = 0; i < n; i++) {
|
||||
const iteratorResult = await tracksIterator.next()
|
||||
|
||||
if (iteratorResult.value) {
|
||||
update(entries => [...entries, iteratorResult.value])
|
||||
} else {
|
||||
// iterator don't have new entries for now
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const unshift = async () => {
|
||||
let promise = promise.then(() => {
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
promise = load()
|
||||
|
||||
return { subscribe, unshift }
|
||||
}
|
||||
|
||||
export const entryStore = (entriesStore) => {
|
||||
const store = writable(null)
|
||||
const { set, update, subscribe } = store
|
||||
|
||||
const select = (entry) => {
|
||||
update(() => entry)
|
||||
|
||||
const entriesList = get(entriesStore)
|
||||
const index = entriesList.indexOf(entry)
|
||||
|
||||
if (index === entriesList.length - 1) {
|
||||
entriesStore.load(1)
|
||||
}
|
||||
}
|
||||
|
||||
const previous = () => {
|
||||
const entriesList = get(entriesStore)
|
||||
|
||||
update(currentEntry => {
|
||||
const index = entriesList.indexOf(currentEntry)
|
||||
|
||||
return index > 0 ? entriesList[index - 1] : null
|
||||
})
|
||||
}
|
||||
|
||||
const next = () => {
|
||||
const entriesList = get(entriesStore)
|
||||
|
||||
update(oldEntry => {
|
||||
if (entriesList.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
const index = entriesList.indexOf(oldEntry)
|
||||
|
||||
if (index === -1) {
|
||||
return null
|
||||
}
|
||||
|
||||
const nextIndex = index + 1
|
||||
|
||||
if (nextIndex === entriesList.length - 1) {
|
||||
entries.load(1)
|
||||
}
|
||||
|
||||
return entriesList[nextIndex]
|
||||
})
|
||||
}
|
||||
|
||||
return { subscribe, set: select, previous, next }
|
||||
}
|
||||
|
||||
export const entriesStore = (domain, hashtags) => {
|
||||
const tracksIterator = mkTracksIterator(domain, hashtags)
|
||||
|
||||
const store = writable([])
|
||||
const { update, subscribe } = store
|
||||
|
||||
const load = async (number) => {
|
||||
for (let i = 0; i < number; i++) {
|
||||
const iteratorResult = await tracksIterator.next()
|
||||
|
||||
if (iteratorResult.value) {
|
||||
update(entries => [...entries, iteratorResult.value])
|
||||
} else {
|
||||
// iterator don't have new entries for now
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { subscribe, load }
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
import { writable, derived, get } from 'svelte/store'
|
||||
import { writableLocalStorage, mkTracksIterator } from '/util.js'
|
||||
|
||||
export const domain = writableLocalStorage('domain', 'eldritch.cafe')
|
||||
|
||||
export const hashtags = writableLocalStorage('hashtags', [
|
||||
'np',
|
||||
'nowplaying',
|
||||
'tootradio',
|
||||
'pouetradio'
|
||||
])
|
||||
|
||||
export const paused = writable(false)
|
||||
export const muted = writableLocalStorage('muted', false)
|
||||
export const volume = writableLocalStorage('volume', 100)
|
||||
|
||||
export const index = writable(null)
|
||||
export const queue = writable([])
|
||||
export const enqueueing = writable(false)
|
||||
export const current = derived([queue, index], ([$queue, $index]) => $queue[$index])
|
||||
|
||||
export const canPrevious = derived([index], ([$index]) => $index !== null && $index > 0)
|
||||
export const canNext = derived([index, queue], ([$index, $queue]) => $index !== null && $index < $queue.length - 1)
|
||||
|
||||
export const loading = writable(false)
|
||||
|
||||
|
||||
|
||||
const tracksIterator = mkTracksIterator(get(domain), get(hashtags))
|
||||
|
||||
export const selectPrevious = () => { if (get(canPrevious)) index.update($index => $index - 1) }
|
||||
export const selectNext = () => { if (get(canNext)) index.update($index => $index + 1) }
|
||||
|
||||
export const enqueue = async () => {
|
||||
if (!get(enqueueing)) {
|
||||
enqueueing.set(true)
|
||||
|
||||
const { value: newTrack } = await tracksIterator.next()
|
||||
|
||||
if (newTrack) {
|
||||
queue.update($queue => [...$queue, newTrack])
|
||||
}
|
||||
|
||||
enqueueing.set(false)
|
||||
}
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
import { writable, get } from 'svelte/store'
|
||||
import { writableLocalStorage, entriesStore, entryStore } from '/services/store.js'
|
||||
|
||||
export const domain = writableLocalStorage('domain', 'eldritch.cafe')
|
||||
|
||||
export const hashtags = writableLocalStorage('hashtags', [
|
||||
'np',
|
||||
'nowplaying',
|
||||
'tootradio',
|
||||
'pouetradio'
|
||||
])
|
||||
|
||||
export const paused = writable(false)
|
||||
export const muted = writableLocalStorage('muted', false)
|
||||
export const volume = writableLocalStorage('volume', 100)
|
||||
|
||||
export const entries = entriesStore(get(domain), get(hashtags))
|
||||
export const entry = entryStore(entries)
|
||||
|
||||
|
||||
|
||||
|
||||
const tracksIterator = mkTracksIterator(get(domain), get(hashtags))
|
||||
|
||||
|
||||
|
||||
export const track = writable(null)
|
||||
export const queue = writable([])
|
||||
export const stack = writable([])
|
||||
|
||||
export const state = writable({
|
||||
current: null,
|
||||
queue: []
|
||||
})
|
||||
|
||||
export const enqueue = () => {
|
||||
const { value: newTrack } = await tracksIterator.next()
|
||||
|
||||
if (!newTrack) {
|
||||
state.update(s => ({ ...s, queue: [...s.queue, newTrack] }))
|
||||
}
|
||||
}
|
||||
|
||||
export const select = (track) => {
|
||||
state.update(s => ({ ...s, current: track }))
|
||||
}
|
||||
|
||||
export const selectPrevious = () => {
|
||||
state.update(s => {
|
||||
if (s.current === null) return s
|
||||
|
||||
const
|
||||
return
|
||||
})
|
||||
|
||||
const tracks = get(queue)
|
||||
|
||||
track.update(oldTrack => {
|
||||
const index = tracks.indexOf(oldTrack)
|
||||
return index > 0 ? tracks[index - 1] : null
|
||||
})
|
||||
}
|
||||
|
||||
export const selectNext = () => {
|
||||
const tracks = get(queue)
|
||||
const oldTrack = get(track)
|
||||
|
||||
|
||||
|
||||
track.update(oldTrack => {
|
||||
const index = tracks.indexOf(oldTrack)
|
||||
|
||||
if (index !== -1 && ) {
|
||||
return null
|
||||
}
|
||||
|
||||
return index !== -1 && index + 1 < tracks.length
|
||||
? tracks[index + 1]
|
||||
: null
|
||||
})
|
||||
|
||||
|
||||
enqueueIfTrack(track)
|
||||
}
|
10
src/util.js
10
src/util.js
|
@ -1,6 +1,16 @@
|
|||
import { writable } from 'svelte/store'
|
||||
import getUrls from 'get-urls'
|
||||
import { execPipe, asyncFilter, asyncMap } from 'iter-tools'
|
||||
|
||||
export const writableLocalStorage = (key, value) => {
|
||||
const item = JSON.parse(localStorage.getItem(key))
|
||||
const store = writable(item === null ? value : item)
|
||||
|
||||
store.subscribe(x => localStorage.setItem(key, JSON.stringify(x)))
|
||||
|
||||
return store
|
||||
}
|
||||
|
||||
const millisecond = 1
|
||||
const second = 1000 * millisecond
|
||||
const minute = 60 * second
|
||||
|
|
Loading…
Reference in New Issue