Initial support for offline playback

This commit is contained in:
Shiny Nematoda 2023-06-20 15:53:45 +00:00
parent 1372d87beb
commit 4353cacead
9 changed files with 116 additions and 50 deletions

60
package-lock.json generated
View file

@ -12,7 +12,7 @@
"dompurify": "^3.0.3",
"mux.js": "^6.3.0",
"peerjs": "^1.4.7",
"pinia": "^2.1.3",
"pinia": "^2.1.4",
"shaka-player": "^4.3.6",
"sortablejs": "^1.15.0",
"vue": "^3.2.38"
@ -2242,9 +2242,9 @@
"dev": true
},
"node_modules/@types/node": {
"version": "20.3.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.0.tgz",
"integrity": "sha512-cumHmIAf6On83X7yP+LrsEyUOf/YlociZelmpRYaGFydoaPdxdt80MAbu6vWerQT2COCp2nPvHdsbD7tHn/YlQ==",
"version": "20.3.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.1.tgz",
"integrity": "sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg==",
"dev": true
},
"node_modules/@types/resolve": {
@ -2405,9 +2405,9 @@
"integrity": "sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ=="
},
"node_modules/acorn": {
"version": "8.8.2",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
"integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==",
"version": "8.9.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz",
"integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==",
"dev": true,
"bin": {
"acorn": "bin/acorn"
@ -2567,9 +2567,9 @@
}
},
"node_modules/browserslist": {
"version": "4.21.7",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.7.tgz",
"integrity": "sha512-BauCXrQ7I2ftSqd2mvKHGo85XR0u7Ru3C/Hxsy/0TkfCtjrmAbPdzLGasmoiBxplpDXlPvdjX9u7srIMfgasNA==",
"version": "4.21.9",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.9.tgz",
"integrity": "sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==",
"dev": true,
"funding": [
{
@ -2586,8 +2586,8 @@
}
],
"dependencies": {
"caniuse-lite": "^1.0.30001489",
"electron-to-chromium": "^1.4.411",
"caniuse-lite": "^1.0.30001503",
"electron-to-chromium": "^1.4.431",
"node-releases": "^2.0.12",
"update-browserslist-db": "^1.0.11"
},
@ -2630,9 +2630,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001499",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001499.tgz",
"integrity": "sha512-IhoQqRrW6WiecFcfZgoJS1YLEN1/HR1vHP5WNgjCARRW7KUNToHHTX3FrwCM+y4zkRa48D9rE90WFYc2IWhDWQ==",
"version": "1.0.30001505",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001505.tgz",
"integrity": "sha512-jaAOR5zVtxHfL0NjZyflVTtXm3D3J9P15zSJ7HmQF8dSKGA6tqzQq+0ZI3xkjyQj46I4/M0K2GbMpcAFOcbr3A==",
"dev": true,
"funding": [
{
@ -2706,9 +2706,9 @@
"dev": true
},
"node_modules/core-js-compat": {
"version": "3.30.2",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.30.2.tgz",
"integrity": "sha512-nriW1nuJjUgvkEjIot1Spwakz52V9YkYHZAQG6A1eCgC8AA1p0zngrQEP9R0+V6hji5XilWKG1Bd0YRppmGimA==",
"version": "3.31.0",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.31.0.tgz",
"integrity": "sha512-hM7YCu1cU6Opx7MXNu0NuumM0ezNeAeRKadixyiQELWY3vT3De9S4J5ZBMraWV2vZnrE1Cirl0GtFtDtMUXzPw==",
"dev": true,
"dependencies": {
"browserslist": "^4.21.5"
@ -2800,9 +2800,9 @@
}
},
"node_modules/electron-to-chromium": {
"version": "1.4.427",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.427.tgz",
"integrity": "sha512-HK3r9l+Jm8dYAm1ctXEWIC+hV60zfcjS9UA5BDlYvnI5S7PU/yytjpvSrTNrSSRRkuu3tDyZhdkwIczh+0DWaw==",
"version": "1.4.434",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.434.tgz",
"integrity": "sha512-5Gvm09UZTQRaWrimRtWRO5rvaX6Kpk5WHAPKDa7A4Gj6NIPuJ8w8WNpnxCXdd+CJJt6RBU6tUw0KyULoW6XuHw==",
"dev": true
},
"node_modules/eme-encryption-scheme-polyfill": {
@ -4056,9 +4056,9 @@
}
},
"node_modules/pinia": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/pinia/-/pinia-2.1.3.tgz",
"integrity": "sha512-XNA/z/ye4P5rU1pieVmh0g/hSuDO98/a5UC8oSP0DNdvt6YtetJNHTrXwpwsQuflkGT34qKxAEcp7lSxXNjf/A==",
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/pinia/-/pinia-2.1.4.tgz",
"integrity": "sha512-vYlnDu+Y/FXxv1ABo1vhjC+IbqvzUdiUC3sfDRrRyY2CQSrqqaa+iiHmqtARFxJVqWQMCJfXx1PBvFs9aJVLXQ==",
"dependencies": {
"@vue/devtools-api": "^6.5.0",
"vue-demi": ">=0.14.5"
@ -4329,9 +4329,9 @@
}
},
"node_modules/rollup": {
"version": "3.25.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.25.0.tgz",
"integrity": "sha512-FnJkNRst2jEZGw7f+v4hFo6UTzpDKrAKcHZWcEfm5/GJQ5CK7wgb4moNLNAe7npKUev7yQn1AY/YbZRIxOv6Qg==",
"version": "3.25.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.25.1.tgz",
"integrity": "sha512-tywOR+rwIt5m2ZAWSe5AIJcTat8vGlnPFAv15ycCrw33t6iFsXZ6mzHVFh2psSjxQPmI+xgzMZZizUAukBI4aQ==",
"dev": true,
"bin": {
"rollup": "dist/bin/rollup"
@ -4651,9 +4651,9 @@
}
},
"node_modules/terser": {
"version": "5.17.7",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.17.7.tgz",
"integrity": "sha512-/bi0Zm2C6VAexlGgLlVxA0P2lru/sdLyfCVaRMfKVo9nWxbmz7f/sD8VPybPeSUJaJcwmCJis9pBIhcVcG1QcQ==",
"version": "5.18.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.18.1.tgz",
"integrity": "sha512-j1n0Ao919h/Ai5r43VAnfV/7azUYW43GPxK7qSATzrsERfW7+y2QW9Cp9ufnRF5CQUWbnLSo7UJokSWCqg4tsQ==",
"dev": true,
"dependencies": {
"@jridgewell/source-map": "^0.3.3",

View file

@ -14,7 +14,7 @@
"dompurify": "^3.0.3",
"mux.js": "^6.3.0",
"peerjs": "^1.4.7",
"pinia": "^2.1.3",
"pinia": "^2.1.4",
"shaka-player": "^4.3.6",
"sortablejs": "^1.15.0",
"vue": "^3.2.38"

View file

@ -93,12 +93,12 @@ function parseUrl() {
function playThis(t) {
const i = data.state.urls.indexOf(t);
data.getSong(data.state.urls[i].url);
data.play(data.state.urls[i]);
}
function playList(a) {
data.state.urls = a;
data.getSong(data.state.urls[0].url);
data.play(data.state.urls[0]);
}
/* Lifestyle hooks */

View file

@ -1,3 +1,3 @@
{
"date": "2023-06-13"
"date": "2023-06-20"
}

View file

@ -75,10 +75,8 @@ const setProxy = async () => {
title: 'Local • ' + key,
items: res.urls.map(i => ({
...i,
...{
playlistId: key,
thumbnail: parseThumb(i.url, proxy.value),
},
playlistId: key,
thumbnail: parseThumb(i.url, proxy.value),
})),
});
@ -86,6 +84,23 @@ const setProxy = async () => {
} else alert('No songs to play!');
});
},
OpenOffline = async () => {
if (window.offline) {
const songs = await window.offline.list();
console.log();
results.resetItems();
results.setItem('songs', {
title: 'Hyp • Offline',
items: songs.map(i => ({
...i.appMetadata,
offlineUri: i.offlineUri,
thumbnail: parseThumb(i.appMetadata.url, proxy.value),
duration: i.duration
})),
});
nav.state.page = 'home';
}
},
List = async () => {
if (!proxy.value) await setProxy();
@ -421,6 +436,7 @@ onMounted(async () => {
<h2 v-if="list.length > 0">{{ t('playlist.local') }}</h2>
<div class="grid-3">
<AlbumItem name="Offline" :grad="useRand()" @open-album="OpenOffline()" />
<AlbumItem
v-for="i in list"
:key="i.name"

View file

@ -60,6 +60,18 @@ async function Stream() {
}
});
window.offline = new shaka.offline.Storage(audioPlayer);
window.offline.configure({
offline: {
progressCallback: (data, prog) => console.log(data, prog),
trackSelectionCallback: tracks => [
tracks
.sort((a, b) => a.bandwidth - b.bandwidth)
.find(i => i.type == 'variant'),
],
},
});
audioPlayer.configure({
preferredAudioCodecs: codecs ? codecs.split(':') : ['opus', 'mp4a'],
manifest: {
@ -171,7 +183,7 @@ onMounted(() => {
if (data.state.urls.length > 2) {
const i = data.state.urls.findIndex(s => s.url == data.state.url);
data.getSong(data.state.urls[i - 1].url);
data.play(data.state.urls[i - 1]);
}
});
@ -179,7 +191,7 @@ onMounted(() => {
if (data.state.urls.length > 2) {
const i = data.state.urls.findIndex(s => s.url == data.state.url);
data.getSong(data.state.urls[i + 1].url);
data.play(data.state.urls[i + 1]);
}
});

View file

@ -34,6 +34,9 @@ const shuffleAdd = () => {
url: i.url,
title: i.title,
thumbnails: [{ url: i.thumbnail }],
thumbnail: i.thumbnail,
offlineUri: i.offlineUri,
duration: i.duration
})),
copy = [];
@ -56,9 +59,13 @@ const shuffleAdd = () => {
url: i.url || '/watch?v=' + song.id,
title: i.title,
thumbnails: [{ url: i.thumbnail }],
thumbnail: i.thumbnail,
offlineUri: i.offlineUri,
duration: i.duration
}));
data.getSong(song.url || '/watch?v=' + song.id);
song.url = song.url || '/watch?v=' + song.id;
data.play(song);
} else {
emit('play-urls', [
{
@ -245,6 +252,9 @@ onDeactivated(() => {
url: item.url,
title: item.title,
thumbnails: [{ url: item.thumbnail }],
thumbnail: item.thumbnail,
offlineUri: item.offlineUri,
duration: item.duration
})),
)
" />
@ -313,9 +323,11 @@ onDeactivated(() => {
:key="song.url || song.id"
:index="index"
:playlistId="song.playlistId"
:author="song.uploaderName || song.subtitle"
:author="song.uploaderName || song.artist || song.subtitle"
:title="song.title || song.name"
:channel="song.uploaderUrl || '/channel/' + song.subId"
:channel="
song.uploaderUrl || song.artistUrl || '/channel/' + song.subId
"
:play="song.url || '/watch?v=' + song.id"
:art="
song.thumbnail ||

View file

@ -75,6 +75,17 @@ function Save() {
}
}
async function Offline() {
if (window.offline && data.state.url) {
window.offline.store(window.audioPlayer.getAssetUri(), {
title: data.state.title,
url: data.state.url,
artist: data.state.artist,
artistUrl: data.state.artistUrl,
});
} else console.error('no offline storage found');
}
async function Like() {
liking.value = true;
@ -279,6 +290,12 @@ async function Like() {
:data-loading="liking"
@click="Like"></button>
<button
id="dl-btn"
title="Save for Offline Playback"
class="bi bi-download clickable"
@click="Offline"></button>
<button
id="addToPlaylist"
title="Add Current Song to a Playlist"

View file

@ -39,6 +39,15 @@ export const useData = defineStore('data', () => {
await getNext(hash);
}
async function play(song) {
if (song.offlineUri) {
state.art = song.thumbnail;
player.state.duration = song.duration
for (let i of ['title', 'artist', 'artistUrl', 'url']) state[i] = song[i];
window.audioPlayer.load(song.offlineUri);
} else await getSong(song.url);
}
async function getNext(hash) {
if (
store.getItem('next') !== 'false' &&
@ -97,34 +106,34 @@ export const useData = defineStore('data', () => {
state.urls.length != 0 &&
state.urls[i + 1]
)
getSong(state.urls[i + 1].url);
play(state.urls[i + 1]);
else if (player.state.loop == 1) {
state.url = state.urls[0].url;
getSong(state.urls[0].url);
play(state.urls[0]);
} else state.urls = [];
}
function prevTrack() {
const i = state.urls.findIndex(s => s.url === state.url);
if (state.urls[i - 1]) getSong(state.urls[i - 1].url);
if (state.urls[i - 1]) play(state.urls[i - 1]);
else if (player.state.loop == 1) {
state.url = state.urls[state.urls.length - 1].url;
getSong(state.urls[state.urls.length - 1].url);
play(state.urls[state.urls.length - 1]);
} else state.urls = [];
}
function nextTrack() {
const i = state.urls.findIndex(s => s.url === state.url);
if (state.urls[i + 1]) getSong(state.urls[i + 1].url);
if (state.urls[i + 1]) play(state.urls[i + 1]);
else if (player.state.loop == 1) {
state.url = state.urls[0].url;
getSong(state.urls[0].url);
play(state.urls[0]);
} else state.urls = [];
}
return { state, getSong, playNext, prevTrack, nextTrack };
return { state, getSong, play, playNext, prevTrack, nextTrack };
});
export const usePlayer = defineStore('player', () => {