diff --git a/package-lock.json b/package-lock.json
index d41e541..4f4076e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2312,6 +2312,11 @@
"whatwg-url": "^7.0.0"
}
},
+ "date-fns": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.9.0.tgz",
+ "integrity": "sha512-khbFLu/MlzLjEzy9Gh8oY1hNt/Dvxw3J6Rbc28cVoYWQaC1S3YI4xwkF9ZWcjDLscbZlY9hISMr66RFzZagLsA=="
+ },
"deasync": {
"version": "0.1.19",
"resolved": "https://registry.npmjs.org/deasync/-/deasync-0.1.19.tgz",
@@ -3620,11 +3625,6 @@
"integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=",
"dev": true
},
- "get-youtube-id": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/get-youtube-id/-/get-youtube-id-1.0.1.tgz",
- "integrity": "sha512-5yidLzoLXbtw82a/Wb7LrajkGn29BM6JuLWeHyNfzOGp1weGyW4+7eMz6cP23+etqj27VlOFtq8fFFDMLq/FXQ=="
- },
"getpass": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
diff --git a/package.json b/package.json
index fb3f69a..ba66d4b 100644
--- a/package.json
+++ b/package.json
@@ -19,8 +19,8 @@
},
"dependencies": {
"@babel/runtime-corejs3": "^7.7.7",
+ "date-fns": "^2.9.0",
"get-urls": "^9.2.0",
- "get-youtube-id": "^1.0.1",
"yt-player": "^3.4.3"
},
"browserslist": [
diff --git a/src/App.svelte b/src/App.svelte
index 304f475..ff11287 100644
--- a/src/App.svelte
+++ b/src/App.svelte
@@ -6,38 +6,55 @@
- {#if selectedEntry}
- Playing {selectedEntry.id}
-
+ {#if $selectedEntry}
+ Playing {$selectedEntry.id}
+
{:else}
Loading ...
{/if}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
- {#if $entries}
- {#await $entries}
- Loading radio please wait
- {:then entries}
+
+ {#if $entries}
- {#each entries as entry}
- -
+ {#each $entries as entry}
+
- $selectedEntry = entry}>
+
{entry.url}
{entry.status.account.acct}
{entry.tags}
{/each}
- {:catch error}
- Oops, something went wrong : {error}
- {/await}
+ {:else}
+ Your queue
+ {/if}
+
+
+ {#if $loading}
+ LOADING ...
{:else}
- Your queue
+
{/if}
@@ -46,16 +63,19 @@
@@ -96,6 +116,11 @@
.entry {
cursor: pointer;
+ border: 1px solid black;
+ }
+
+ .entry:hover {
+ background-color: plum;
}
.entry.active {
diff --git a/src/YoutubeViewer.svelte b/src/YoutubeViewer.svelte
index cb47ddb..e154de1 100644
--- a/src/YoutubeViewer.svelte
+++ b/src/YoutubeViewer.svelte
@@ -9,6 +9,7 @@
import YoutubePlayer from 'yt-player'
export let videoId
+ export let playing
let element
let player
@@ -17,9 +18,17 @@
player.load(videoId, true)
}
+ $: if (player) {
+ if (playing) {
+ player.play()
+ } else {
+ player.pause()
+ }
+ }
+
onMount(() => {
player = new YoutubePlayer(element, {
- autoplay: true,
+ autoplay: playing,
controls: true, // debug only
keyboard: false,
fullscreen: false,
diff --git a/src/index.html b/src/index.html
index 6bc2b84..6d81e5a 100644
--- a/src/index.html
+++ b/src/index.html
@@ -4,7 +4,7 @@
-
Document
+ Eldritch Radio
diff --git a/src/store.js b/src/store.js
index 524a26e..22be305 100644
--- a/src/store.js
+++ b/src/store.js
@@ -1,5 +1,5 @@
import { writable, get } from 'svelte/store'
-import { getUrls, getYoutubeId, isSupportedUrl, intersection } from './util.js'
+import * as util from './util.js'
export const domain = writable('eldritch.cafe')
@@ -10,62 +10,91 @@ export const hashtags = writable([
'pouetradio'
])
-export const entries = entriesStore()
+export const playing = writable(true)
-function entriesStore() {
- let loading = false
- let next = `https://eldritch.cafe/api/v1/timelines/tag/np`
+export const loading = writable(false)
+export const entries = entriesStore(loading)
+
+export const entry = entryStore(entries)
+
+
+
+
+
+function entryStore(entries) {
const store = writable(null)
- const { set, subscribe } = store
+ const { set, update, subscribe } = store
- const load = async () => {
- if (loading) {
+ const next = async () => {
+ const entriesList = await get(entries)
+
+ 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, next }
+}
+
+async function* loader(loading) {
+ loading.set(true)
+ let { statuses, nextLink, previousLink } = await util.fetchTimeline('https://eldritch.cafe/api/v1/timelines/tag/np')
+ loading.set(false)
+
+ yield* util.statusesToEntries(statuses)
+
+ while (true) {
+ loading.set(true)
+ const timeline = await util.fetchTimeline(nextLink)
+ loading.set(false)
+
+ nextLink = timeline.nextLink
+
+ yield* util.statusesToEntries(timeline.statuses)
+ }
+}
+
+
+function entriesStore(loading) {
+ const entriesSteam = loader(loading)
+
+ const store = writable([])
+ const { update, subscribe } = store
+
+ const load = async (number) => {
+ if (get(loading)) {
return
}
- loading = true
+ for (let i = 0; i < number; i++) {
+ const iteratorResult = await entriesSteam.next()
- const responseP = fetch(next)
-
- responseP.then(response => {
- next = Array.from(getUrls(response.headers.get('link')))[0] // need to better parse that
- })
-
- const entriesP = responseP
- .then(response => response.json())
- .then(statuses => {
- return statuses
- .map(status => {
- const [url] = Array.from(getUrls(status.content)).filter(isSupportedUrl)
-
- return { status, url }
- })
- .filter(entry => entry.url != null)
- .map(({ status, url }) => {
- const id = getYoutubeId(url)
- const tags = intersection(status.tags.map(tag => tag.name), [
- 'np',
- 'nowplaying',
- 'tootradio',
- 'pouetradio'
- ])
-
- return { status, url, id, tags }
- })
- })
-
- const previousEntriesP = get(store)
-
- if (previousEntriesP) {
- const [previousEntries, entries] = await Promise.all([previousEntriesP, entriesP])
- set(Promise.resolve([...previousEntries, ...entries]))
- } else {
- set(entriesP)
+ if (iteratorResult.value) {
+ update(entries => [...entries, iteratorResult.value])
+ } else {
+ break
+ }
}
-
- loading = false
}
return { subscribe, load }
-}
\ No newline at end of file
+}
+
diff --git a/src/util.js b/src/util.js
index 4d34d8d..c39744f 100644
--- a/src/util.js
+++ b/src/util.js
@@ -1,10 +1,66 @@
-export { default as getUrls } from 'get-urls'
-export { default as getYoutubeId } from 'get-youtube-id'
+import getUrls from 'get-urls'
-export function isSupportedUrl(url) {
- return (new URL(url)).hostname === 'youtube.com'
+export function isSupportedUrl(urlAsString) {
+ const url = new URL(urlAsString)
+
+ const hosts = [
+ 'youtube.com',
+ 'music.youtube.host'
+ ]
+
+ return hosts.includes(url.hostname) && url.searchParams.has('v')
+}
+
+export function getYoutubeVideoId(urlAsString) {
+ return new URL(urlAsString).searchParams.get('v')
}
export function intersection(xs, ys) {
return xs.filter(x => ys.includes(x));
+}
+
+export async function fetchTimeline(url) {
+ const urlBuilder = new URL(url)
+ urlBuilder.searchParams.set('limit', 40)
+
+ const response = await fetch(url)
+ const statuses = await response.json()
+ const { next, previous } = parseLinkHeader(response.headers.get('link'))
+
+ return { statuses, nextLink: next, previousLink: previous }
+}
+
+const LINK_RE = /<(.+?)>; rel="(\w+)"/gi
+
+export function parseLinkHeader(link) {
+ const links = {}
+
+ for (const [ , url, name ] of link.matchAll(LINK_RE)) {
+ links[name] = url
+ }
+
+ return links
+}
+
+export function statusesToEntries(statuses) {
+ const entries = []
+
+ return statuses
+ .map(status => {
+ const [url] = Array.from(getUrls(status.content)).filter(isSupportedUrl)
+
+ return { status, url }
+ })
+ .filter(entry => entry.url != null)
+ .map(({ status, url }) => {
+ const id = getYoutubeVideoId(url)
+ const tags = intersection(status.tags.map(tag => tag.name), [
+ 'np',
+ 'nowplaying',
+ 'tootradio',
+ 'pouetradio'
+ ])
+
+ return { status, url, id, tags }
+ })
}
\ No newline at end of file