@@ -20,7 +20,6 @@ jobs:
|
||||
node-version: [latest]
|
||||
runtime:
|
||||
- linux-x64
|
||||
- win-x64
|
||||
- osx-x64
|
||||
|
||||
include:
|
||||
@@ -28,8 +27,6 @@ jobs:
|
||||
os: ubuntu-latest
|
||||
- runtime: osx-x64
|
||||
os: macOS-latest
|
||||
- runtime: win-x64
|
||||
os: windows-latest
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
- Dash support.
|
||||
- Chapters.
|
||||
- Audio only mode.
|
||||
- Mini player (PIP.)
|
||||
- Playlists.
|
||||
- PWA support.
|
||||
- YT path redirects (So your redirect plugins should still work!)
|
||||
|
||||
@@ -7,8 +7,8 @@ android {
|
||||
applicationId "us.materialio.app"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 162
|
||||
versionName "1.11.2"
|
||||
versionCode 163
|
||||
versionName "1.11.3"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
aaptOptions {
|
||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||
|
||||
@@ -72,7 +72,11 @@
|
||||
|
||||
|
||||
|
||||
<release version="1.11.2" date="2025-11-05">
|
||||
|
||||
<release version="1.11.3" date="2025-11-07">
|
||||
<url>https://github.com/Materialious/Materialious/releases/tag/1.11.3</url>
|
||||
</release>
|
||||
<release version="1.11.2" date="2025-11-05">
|
||||
<url>https://github.com/Materialious/Materialious/releases/tag/1.11.2</url>
|
||||
</release>
|
||||
<release version="1.11.1" date="2025-11-05">
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "Materialious",
|
||||
"version": "1.11.2",
|
||||
"version": "1.11.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "Materialious",
|
||||
"version": "1.11.2",
|
||||
"version": "1.11.3",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@capacitor-community/electron": "^5.0.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Materialious",
|
||||
"version": "1.11.2",
|
||||
"version": "1.11.3",
|
||||
"description": "Modern material design for Invidious.",
|
||||
"author": {
|
||||
"name": "Ward Pearce",
|
||||
@@ -24,6 +24,7 @@
|
||||
"dependencies": {
|
||||
"@capacitor-community/electron": "^5.0.0",
|
||||
"bgutils-js": "^3.2.0",
|
||||
"capacitor-nodejs": "https://github.com/hampoelz/capacitor-nodejs/releases/download/v1.0.0-beta.9/capacitor-nodejs.tgz",
|
||||
"chokidar": "~4.0.3",
|
||||
"electron-is-dev": "~2.0.0",
|
||||
"electron-serve": "~3.0.0",
|
||||
@@ -43,4 +44,4 @@
|
||||
"capacitor",
|
||||
"electron"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const CapacitorNodejs = require('../../../node_modules/capacitor-nodejs/electron/dist/plugin.js');
|
||||
|
||||
module.exports = {
|
||||
CapacitorNodejs,
|
||||
}
|
||||
Generated
+27
-112
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "materialious",
|
||||
"version": "1.11.0",
|
||||
"version": "1.11.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "materialious",
|
||||
"version": "1.11.0",
|
||||
"version": "1.11.3",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@capacitor-community/electron": "^5.0.1",
|
||||
@@ -17,7 +17,7 @@
|
||||
"@capacitor/clipboard": "^7.0.0",
|
||||
"@capacitor/core": "^7.4.3",
|
||||
"@capacitor/screen-orientation": "^7.0.0",
|
||||
"beercss": "^3.12.11",
|
||||
"beercss": "^3.12.13",
|
||||
"bgutils-js": "^3.2.0",
|
||||
"capacitor-nodejs": "https://github.com/hampoelz/capacitor-nodejs/releases/download/v1.0.0-beta.9/capacitor-nodejs.tgz",
|
||||
"dayjs": "^1.11.19",
|
||||
@@ -42,9 +42,9 @@
|
||||
"@capacitor/assets": "^3.0.5",
|
||||
"@capacitor/cli": "^7.4.3",
|
||||
"@sveltejs/adapter-auto": "^6.0.1",
|
||||
"@sveltejs/adapter-static": "^3.0.8",
|
||||
"@sveltejs/adapter-static": "^3.0.10",
|
||||
"@sveltejs/kit": "^2.46.5",
|
||||
"@sveltejs/vite-plugin-svelte": "^6.1.4",
|
||||
"@sveltejs/vite-plugin-svelte": "^6.2.1",
|
||||
"@types/eslint": "^8.56.7",
|
||||
"@types/event-source-polyfill": "^1.0.5",
|
||||
"@types/he": "^1.2.3",
|
||||
@@ -56,7 +56,7 @@
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-svelte": "^3.11.0",
|
||||
"patch-package": "^8.0.0",
|
||||
"patch-package": "^8.0.1",
|
||||
"prettier": "^3.6.2",
|
||||
"prettier-plugin-svelte": "^3.4.0",
|
||||
"svelte": "^5.43.2",
|
||||
@@ -3249,9 +3249,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sveltejs/adapter-static": {
|
||||
"version": "3.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.8.tgz",
|
||||
"integrity": "sha512-YaDrquRpZwfcXbnlDsSrBQNCChVOT9MGuSg+dMAyfsAa1SmiAhrA5jUYUiIMC59G92kIbY/AaQOWcBdq+lh+zg==",
|
||||
"version": "3.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.10.tgz",
|
||||
"integrity": "sha512-7D9lYFWJmB7zxZyTE/qxjksvMqzMuYrrsyh1f4AlZqeZeACPRySjbC3aFiY55wb1tWUaKOQG9PVbm74JcN2Iew==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
@@ -3298,9 +3298,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sveltejs/vite-plugin-svelte": {
|
||||
"version": "6.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.1.4.tgz",
|
||||
"integrity": "sha512-4jfkfvsGI+U2OhHX8OPCKtMCf7g7ledXhs3E6UcA4EY0jQWsiVbe83pTAHp9XTifzYNOiD4AJieJUsI0qqxsbw==",
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.2.1.tgz",
|
||||
"integrity": "sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -4294,9 +4294,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/beercss": {
|
||||
"version": "3.12.11",
|
||||
"resolved": "https://registry.npmjs.org/beercss/-/beercss-3.12.11.tgz",
|
||||
"integrity": "sha512-9NQWL3kxSOb5RPWAV5BPVs64CS72F5Z4HgxusqD07Ho4asaTJB33/SkJBMclQeUQs7hFH2CJOgGgDnIsRk2RyQ==",
|
||||
"version": "3.12.13",
|
||||
"resolved": "https://registry.npmjs.org/beercss/-/beercss-3.12.13.tgz",
|
||||
"integrity": "sha512-zsg15us8pSLTupIOzFFJqpJ7eJDLV6whsxT6GcRYGnYaF0ka45+HQ/u3+ldSUkCeyA5J24g6csEuMW8tqrI/0w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"material-dynamic-colors": "^1.1.2"
|
||||
@@ -9159,16 +9159,6 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/os-tmpdir": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
|
||||
"integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/own-keys": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz",
|
||||
@@ -9279,9 +9269,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/patch-package": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.0.tgz",
|
||||
"integrity": "sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA==",
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.1.tgz",
|
||||
"integrity": "sha512-VsKRIA8f5uqHQ7NGhwIna6Bx6D9s/1iXlA1hthBVBEbkq+t4kXD0HHt+rJhf/Z+Ci0F/HCB2hvn0qLdLG+Qxlw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -9290,15 +9280,14 @@
|
||||
"ci-info": "^3.7.0",
|
||||
"cross-spawn": "^7.0.3",
|
||||
"find-yarn-workspace-root": "^2.0.0",
|
||||
"fs-extra": "^9.0.0",
|
||||
"fs-extra": "^10.0.0",
|
||||
"json-stable-stringify": "^1.0.2",
|
||||
"klaw-sync": "^6.0.0",
|
||||
"minimist": "^1.2.6",
|
||||
"open": "^7.4.2",
|
||||
"rimraf": "^2.6.3",
|
||||
"semver": "^7.5.3",
|
||||
"slash": "^2.0.0",
|
||||
"tmp": "^0.0.33",
|
||||
"tmp": "^0.2.4",
|
||||
"yaml": "^2.2.2"
|
||||
},
|
||||
"bin": {
|
||||
@@ -9309,66 +9298,19 @@
|
||||
"npm": ">5"
|
||||
}
|
||||
},
|
||||
"node_modules/patch-package/node_modules/brace-expansion": {
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/patch-package/node_modules/fs-extra": {
|
||||
"version": "9.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
|
||||
"integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
|
||||
"version": "10.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
|
||||
"integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"at-least-node": "^1.0.0",
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/patch-package/node_modules/glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
||||
"deprecated": "Glob versions prior to v9 are no longer supported",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.1.1",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/patch-package/node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/patch-package/node_modules/open": {
|
||||
@@ -9388,20 +9330,6 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/patch-package/node_modules/rimraf": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
|
||||
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
|
||||
"deprecated": "Rimraf versions prior to v4 are no longer supported",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"glob": "^7.1.3"
|
||||
},
|
||||
"bin": {
|
||||
"rimraf": "bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/patch-package/node_modules/slash": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
|
||||
@@ -9412,19 +9340,6 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/patch-package/node_modules/tmp": {
|
||||
"version": "0.0.33",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
|
||||
"integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"os-tmpdir": "~1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/path-exists": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||
@@ -12025,9 +11940,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tmp": {
|
||||
"version": "0.2.3",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz",
|
||||
"integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==",
|
||||
"version": "0.2.5",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz",
|
||||
"integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "materialious",
|
||||
"version": "1.11.2",
|
||||
"version": "1.11.3",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
@@ -17,9 +17,9 @@
|
||||
"@capacitor/assets": "^3.0.5",
|
||||
"@capacitor/cli": "^7.4.3",
|
||||
"@sveltejs/adapter-auto": "^6.0.1",
|
||||
"@sveltejs/adapter-static": "^3.0.8",
|
||||
"@sveltejs/adapter-static": "^3.0.10",
|
||||
"@sveltejs/kit": "^2.46.5",
|
||||
"@sveltejs/vite-plugin-svelte": "^6.1.4",
|
||||
"@sveltejs/vite-plugin-svelte": "^6.2.1",
|
||||
"@types/eslint": "^8.56.7",
|
||||
"@types/event-source-polyfill": "^1.0.5",
|
||||
"@types/he": "^1.2.3",
|
||||
@@ -31,7 +31,7 @@
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-svelte": "^3.11.0",
|
||||
"patch-package": "^8.0.0",
|
||||
"patch-package": "^8.0.1",
|
||||
"prettier": "^3.6.2",
|
||||
"prettier-plugin-svelte": "^3.4.0",
|
||||
"svelte": "^5.43.2",
|
||||
@@ -50,7 +50,7 @@
|
||||
"@capacitor/clipboard": "^7.0.0",
|
||||
"@capacitor/core": "^7.4.3",
|
||||
"@capacitor/screen-orientation": "^7.0.0",
|
||||
"beercss": "^3.12.11",
|
||||
"beercss": "^3.12.13",
|
||||
"bgutils-js": "^3.2.0",
|
||||
"capacitor-nodejs": "https://github.com/hampoelz/capacitor-nodejs/releases/download/v1.0.0-beta.9/capacitor-nodejs.tgz",
|
||||
"dayjs": "^1.11.19",
|
||||
|
||||
@@ -145,11 +145,15 @@ export async function getChannel(
|
||||
return await resp.json();
|
||||
}
|
||||
|
||||
export type channelSortBy = 'oldest' | 'newest' | 'popular';
|
||||
export type channelContentTypes = 'videos' | 'playlists' | 'streams' | 'shorts';
|
||||
|
||||
export async function getChannelContent(
|
||||
channelId: string,
|
||||
parameters: {
|
||||
type?: 'videos' | 'playlists' | 'streams' | 'shorts';
|
||||
type?: channelContentTypes;
|
||||
continuation?: string;
|
||||
sortBy?: channelSortBy;
|
||||
},
|
||||
fetchOptions?: RequestInit
|
||||
): Promise<ChannelContentVideos | ChannelContentPlaylists> {
|
||||
@@ -160,10 +164,23 @@ export async function getChannelContent(
|
||||
if (typeof parameters.continuation !== 'undefined')
|
||||
url.searchParams.set('continuation', parameters.continuation);
|
||||
|
||||
if (typeof parameters.sortBy !== 'undefined') url.searchParams.set('sort_by', parameters.sortBy);
|
||||
|
||||
const resp = await fetchErrorHandle(await fetch(url.toString(), fetchOptions));
|
||||
return await resp.json();
|
||||
}
|
||||
|
||||
export async function searchChannelContent(
|
||||
channelId: string,
|
||||
search: string,
|
||||
fetchOptions?: RequestInit
|
||||
) {
|
||||
const path = buildPath(`channel/${channelId}/search`);
|
||||
path.search = new URLSearchParams({ q: search }).toString();
|
||||
const resp = await fetchErrorHandle(await fetch(path, fetchOptions));
|
||||
return await resp.json();
|
||||
}
|
||||
|
||||
export async function getSearchSuggestions(
|
||||
search: string,
|
||||
fetchOptions?: RequestInit
|
||||
|
||||
@@ -162,14 +162,6 @@
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
if ($feedLastItemId && !$isAndroidTvStore) {
|
||||
document
|
||||
.getElementById($feedLastItemId)
|
||||
?.scrollIntoView({ behavior: 'instant', block: 'center', inline: 'nearest' });
|
||||
|
||||
feedLastItemId.set(undefined);
|
||||
}
|
||||
|
||||
if ($isAndroidTvStore) {
|
||||
await tick();
|
||||
// Setup Android TV navigation
|
||||
|
||||
@@ -18,14 +18,13 @@
|
||||
import { _ } from '$lib/i18n';
|
||||
import { get } from 'svelte/store';
|
||||
import { deleteVideoProgress, getVideoProgress, saveVideoProgress } from '../api';
|
||||
import type { PlaylistPageVideo, VideoPlay } from '../api/model';
|
||||
import type { VideoPlay } from '../api/model';
|
||||
import {
|
||||
authStore,
|
||||
darkModeStore,
|
||||
instanceStore,
|
||||
isAndroidTvStore,
|
||||
playerAndroidLockOrientation,
|
||||
playerAutoplayNextByDefaultStore,
|
||||
playerAutoPlayStore,
|
||||
playerCCByDefault,
|
||||
playerDefaultLanguage,
|
||||
@@ -33,26 +32,21 @@
|
||||
playerDefaultQualityStore,
|
||||
playerProxyVideosStore,
|
||||
playerSavePlaybackPositionStore,
|
||||
playerState,
|
||||
playerStatisticsByDefault,
|
||||
playerYouTubeJsFallback,
|
||||
playlistSettingsStore,
|
||||
sponsorBlockCategoriesStore,
|
||||
sponsorBlockDisplayToastStore,
|
||||
sponsorBlockStore,
|
||||
sponsorBlockUrlStore,
|
||||
synciousInstanceStore,
|
||||
synciousStore,
|
||||
syncPartyConnectionsStore,
|
||||
themeColorStore
|
||||
} from '../store';
|
||||
import { getDynamicTheme, setStatusBarColor } from '../theme';
|
||||
import { patchYoutubeJs } from '$lib/patches/youtubejs';
|
||||
import { goToNextVideo, goToPreviousVideo, playbackRates } from '$lib/player';
|
||||
import { EndTimeElement } from '$lib/shaka-elements/endTime';
|
||||
import { loadEntirePlaylist } from '$lib/playlist';
|
||||
import { goto } from '$app/navigation';
|
||||
import { unsafeRandomItem } from '$lib/misc';
|
||||
import type { PlayerEvents } from '$lib/player';
|
||||
import { dashManifestDomainInclusion } from '$lib/android/youtube/dash';
|
||||
import { injectSabr } from '$lib/sabr';
|
||||
import type { SabrStreamingAdapter } from 'googlevideo/sabr-streaming-adapter';
|
||||
@@ -61,16 +55,12 @@
|
||||
data: { video: VideoPlay; content: PhasedDescription; playlistId: string | null };
|
||||
isSyncing?: boolean;
|
||||
isEmbed?: boolean;
|
||||
segments?: Segment[];
|
||||
playerElement?: HTMLMediaElement | undefined;
|
||||
}
|
||||
|
||||
let {
|
||||
data,
|
||||
isEmbed = false,
|
||||
segments = $bindable([]),
|
||||
playerElement = $bindable(undefined)
|
||||
}: Props = $props();
|
||||
let { data, isEmbed = false, playerElement = $bindable(undefined) }: Props = $props();
|
||||
|
||||
let segments: Segment[] = [];
|
||||
|
||||
let snackBarAlert = $state('');
|
||||
let originalOrigination: ScreenOrientationResult | undefined;
|
||||
@@ -352,7 +342,6 @@
|
||||
}
|
||||
|
||||
if (data.video.captions) {
|
||||
console.log(data.video.captions);
|
||||
for (const caption of data.video.captions) {
|
||||
let captionUrl: string;
|
||||
if (!import.meta.env.VITE_DEFAULT_COMPANION_INSTANCE) {
|
||||
@@ -470,6 +459,24 @@
|
||||
});
|
||||
playerElement = document.getElementById('player') as HTMLMediaElement;
|
||||
|
||||
if ($playerState) {
|
||||
playerState.set({ ...$playerState, playerElement: playerElement });
|
||||
}
|
||||
|
||||
// Due to how our player is rendered in layout for stateful pip
|
||||
// we calaculate player height to then allow children pages
|
||||
// to wrap around it.
|
||||
function updateVideoPlayerHeight() {
|
||||
const container = document.getElementById('shaka-container') as HTMLElement;
|
||||
|
||||
if (container) {
|
||||
const height = container.getBoundingClientRect().height;
|
||||
document.documentElement.style.setProperty('--video-player-height', `${height + 10}px`);
|
||||
}
|
||||
}
|
||||
window.addEventListener('resize', updateVideoPlayerHeight);
|
||||
updateVideoPlayerHeight();
|
||||
|
||||
// Change instantly to stop video from being loud for a second
|
||||
restoreVolumePreference();
|
||||
|
||||
|
||||
@@ -25,9 +25,6 @@
|
||||
if (get(interfaceLowBandwidthMode)) return;
|
||||
|
||||
let imgSrc = '';
|
||||
|
||||
console.log(playlist);
|
||||
|
||||
if (playlist.videos.length > 0) {
|
||||
imgSrc = getBestThumbnail(playlist.videos[0].videoThumbnails) || '';
|
||||
} else if (playlist.playlistThumbnail.length > 0) {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
playerDefaultLanguage,
|
||||
playerDefaultPlaybackSpeed,
|
||||
playerDefaultQualityStore,
|
||||
playerMiniplayerEnabled,
|
||||
playerProxyVideosStore,
|
||||
playerSavePlaybackPositionStore,
|
||||
playerStatisticsByDefault,
|
||||
@@ -71,7 +72,7 @@
|
||||
bind:value={defaultLanguage}
|
||||
onchange={() => playerDefaultLanguage.set(defaultLanguage)}
|
||||
>
|
||||
<option selected={$playerDefaultLanguage === "original"} value="original">Original</option>
|
||||
<option selected={$playerDefaultLanguage === 'original'} value="original">Original</option>
|
||||
{#each languageNames as language}
|
||||
<option selected={$playerDefaultLanguage === language} value={language}
|
||||
>{titleCase(language)}</option
|
||||
@@ -154,6 +155,23 @@
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="field no-margin">
|
||||
<nav class="no-padding">
|
||||
<div class="max">
|
||||
<div>{$_('layout.player.miniPlayer')}</div>
|
||||
</div>
|
||||
<label class="switch" tabindex="0">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$playerMiniplayerEnabled}
|
||||
onclick={() => playerMiniplayerEnabled.set(!$playerMiniplayerEnabled)}
|
||||
role="switch"
|
||||
/>
|
||||
<span></span>
|
||||
</label>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="field no-margin">
|
||||
<nav class="no-padding">
|
||||
<div class="max">
|
||||
|
||||
@@ -6,8 +6,11 @@
|
||||
import { authStore, interfaceLowBandwidthMode, isAndroidTvStore } from '$lib/store';
|
||||
import { _ } from '$lib/i18n';
|
||||
|
||||
let { video, subscribed = $bindable(false) }: { video: VideoPlay; subscribed?: boolean } =
|
||||
$props();
|
||||
let {
|
||||
video,
|
||||
subscribed = $bindable(false),
|
||||
hideSubscribe = false
|
||||
}: { video: VideoPlay; subscribed?: boolean; hideSubscribe?: boolean } = $props();
|
||||
|
||||
async function toggleSubscribed() {
|
||||
if (subscribed) {
|
||||
@@ -39,24 +42,26 @@
|
||||
</div>
|
||||
</nav>
|
||||
</a>
|
||||
{#if $authStore}
|
||||
<button
|
||||
onclick={toggleSubscribed}
|
||||
class:inverse-surface={!subscribed}
|
||||
class:border={subscribed}
|
||||
>
|
||||
{#if !subscribed}
|
||||
{#if !hideSubscribe}
|
||||
{#if $authStore}
|
||||
<button
|
||||
onclick={toggleSubscribed}
|
||||
class:inverse-surface={!subscribed}
|
||||
class:border={subscribed}
|
||||
>
|
||||
{#if !subscribed}
|
||||
{$_('subscribe')}
|
||||
{:else}
|
||||
{$_('unsubscribe')}
|
||||
{/if}
|
||||
</button>
|
||||
{:else}
|
||||
<button class="inverse-surface" disabled>
|
||||
{$_('subscribe')}
|
||||
{:else}
|
||||
{$_('unsubscribe')}
|
||||
{/if}
|
||||
</button>
|
||||
{:else}
|
||||
<button class="inverse-surface" disabled>
|
||||
{$_('subscribe')}
|
||||
<div class="tooltip">
|
||||
{$_('loginRequired')}
|
||||
</div>
|
||||
</button>
|
||||
<div class="tooltip">
|
||||
{$_('loginRequired')}
|
||||
</div>
|
||||
</button>
|
||||
{/if}
|
||||
{/if}
|
||||
</nav>
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
:root {
|
||||
--video-player-height: 0px;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Inter;
|
||||
src: url('/static/inter.ttf');
|
||||
|
||||
@@ -35,7 +35,8 @@ import {
|
||||
interfaceAutoExpandChapters,
|
||||
playerDefaultPlaybackSpeed,
|
||||
playerStatisticsByDefault,
|
||||
playerCCByDefault
|
||||
playerCCByDefault,
|
||||
playerMiniplayerEnabled
|
||||
} from '$lib/store';
|
||||
import { get, type Writable } from 'svelte/store';
|
||||
|
||||
@@ -223,6 +224,11 @@ const persistedStores: {
|
||||
name: 'CCByDefault',
|
||||
store: playerCCByDefault,
|
||||
type: 'boolean'
|
||||
},
|
||||
{
|
||||
name: 'miniplayerEnabled',
|
||||
store: playerMiniplayerEnabled,
|
||||
type: 'boolean'
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -49,7 +49,9 @@
|
||||
"private": "Privat",
|
||||
"playVideos": "Videos abspielen",
|
||||
"shuffleVideos": "Videos in zufälliger Reihenfolge abspielen",
|
||||
"loopPlaylist": "Wiedergabeliste in Schleife abspielen"
|
||||
"loopPlaylist": "Wiedergabeliste in Schleife abspielen",
|
||||
"next": "Weiter",
|
||||
"previous": "Vorheriges"
|
||||
},
|
||||
"thumbnail": {
|
||||
"live": "LIVE",
|
||||
@@ -98,7 +100,8 @@
|
||||
"enableYoutubejsTemp": "Nutze einmalig den lokalen Video-Fallback",
|
||||
"chapters": "Kapitel",
|
||||
"retryText": "Das Video konnte nicht geladen werden, wie möchtest du weiterfahren?",
|
||||
"youtubeJsLoading": "Das Video konnte nicht geladen werden, es wird mit lokalem Video-Fallback erneut versucht."
|
||||
"youtubeJsLoading": "Das Video konnte nicht geladen werden, es wird mit lokalem Video-Fallback erneut versucht.",
|
||||
"premiere": "Video wird verfügbar sein"
|
||||
},
|
||||
"layout": {
|
||||
"interface": "Benutzeroberfläche",
|
||||
@@ -188,5 +191,14 @@
|
||||
"premium": "YouTube-Premium-Inhalte können auf Materialious nicht angeschaut werden.",
|
||||
"isUpcoming": "Video ist noch nicht in einem anschaubaren Zustand.",
|
||||
"version": "Version",
|
||||
"contributors": "Mitwirkende"
|
||||
"contributors": "Mitwirkende",
|
||||
"popularPageDisabled": "Die Seite „Populär“ wurde von den Administratoren deaktiviert",
|
||||
"filters": {
|
||||
"filters": "Filter",
|
||||
"type": "Typ",
|
||||
"uploadDate": "Hochladedatum",
|
||||
"duration": "Dauer",
|
||||
"sortBy": "Sortieren nach",
|
||||
"features": "Eigenschaften"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"type": "Type",
|
||||
"uploadDate": "Upload date",
|
||||
"duration": "Duration",
|
||||
"sortBy": "Short By",
|
||||
"sortBy": "Sort By",
|
||||
"features": "Features"
|
||||
},
|
||||
"title": "Title",
|
||||
@@ -45,6 +45,9 @@
|
||||
"hideReplies": "Hide replies",
|
||||
"hideVideo": "Hide video",
|
||||
"defaultPage": "Default page",
|
||||
"newest": "Newest",
|
||||
"oldest": "Oldest",
|
||||
"popular": "Popular",
|
||||
"syncParty": {
|
||||
"userJoined": "User joined your watch party.",
|
||||
"userLeft": "User left your watch party.",
|
||||
|
||||
@@ -135,7 +135,9 @@
|
||||
"private": "Privat",
|
||||
"playVideos": "Videos abspiele",
|
||||
"loopPlaylist": "Widergabelischte inere Schleife abspiele",
|
||||
"shuffleVideos": "Videos in zuefälliger Reihefolg abspiele"
|
||||
"shuffleVideos": "Videos in zuefälliger Reihefolg abspiele",
|
||||
"next": "Wiiter",
|
||||
"previous": "Vorherigs"
|
||||
},
|
||||
"pages": {
|
||||
"home": "Startsiite",
|
||||
@@ -179,7 +181,8 @@
|
||||
"retryText": "S Video het nöd chönne glade werde, wie willsch wiitermache?",
|
||||
"enableYoutubejsPerm": "Lokale Video-Fallback immer erlaube",
|
||||
"enableYoutubejsTemp": "Nutz eimalig de lokali Video-Fallback",
|
||||
"youtubeJsFallBack": "Lokale Video-Fallback wird gnutzt"
|
||||
"youtubeJsFallBack": "Lokale Video-Fallback wird gnutzt",
|
||||
"premiere": "Video wird verfüegbar sii"
|
||||
},
|
||||
"subscribe": "Abonniere",
|
||||
"invidiousLogin": "Bitte meld dich mit dim Invidious-Konto aa",
|
||||
@@ -188,5 +191,14 @@
|
||||
"isUpcoming": "Video isch nonig imene aaluegbare Zuestand.",
|
||||
"premium": "YouTube-Premium-Inhält chönd uf Materialious nonig aagluegt werde.",
|
||||
"version": "Version",
|
||||
"contributors": "Mitwirkendi"
|
||||
"contributors": "Mitwirkendi",
|
||||
"popularPageDisabled": "D Siite «Populär» isch vo de Administratore deaktiviert worde",
|
||||
"filters": {
|
||||
"filters": "Filter",
|
||||
"type": "Typ",
|
||||
"uploadDate": "Hochladedatum",
|
||||
"duration": "Duur",
|
||||
"sortBy": "Sortiere nach",
|
||||
"features": "Eigeschafte"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,9 +11,11 @@ import type {
|
||||
PlaylistPage,
|
||||
PlaylistPageVideo,
|
||||
Video,
|
||||
VideoBase
|
||||
VideoBase,
|
||||
VideoPlay
|
||||
} from './api/model';
|
||||
import { ensureNoTrailingSlash } from './misc';
|
||||
import type { PhasedDescription } from './timestamps';
|
||||
|
||||
function platformDependentDefault(givenValue: any, defaultValue: any): any {
|
||||
if (typeof givenValue !== 'undefined' && givenValue !== null) {
|
||||
@@ -57,8 +59,19 @@ export const playerDefaultLanguage = persisted('defaultLanguage', 'original');
|
||||
export const playerCCByDefault = persisted('CCByDefault', false);
|
||||
export const playerDefaultPlaybackSpeed: Writable<number> = persisted('defaultPlaybackSpeed', 1);
|
||||
export const playerStatisticsByDefault = persisted('playerStatistics', false);
|
||||
export const playerMiniplayerEnabled = persisted('miniplayerEnabled', true);
|
||||
export const playerPlaylistHistory: Writable<string[]> = writable([]);
|
||||
|
||||
export const playerState: Writable<
|
||||
| {
|
||||
data: { video: VideoPlay; content: PhasedDescription; playlistId: string | null };
|
||||
isSyncing?: boolean;
|
||||
playerElement?: HTMLMediaElement | undefined;
|
||||
}
|
||||
| undefined
|
||||
> = writable(undefined);
|
||||
export const playertheatreModeIsActive = writable(false);
|
||||
|
||||
export const returnYtDislikesStore = persisted('returnYtDislikes', false);
|
||||
export const returnYTDislikesInstanceStore: Writable<string | null | undefined> = persisted(
|
||||
'returnYTDislikesInstance',
|
||||
@@ -146,4 +159,3 @@ export const playlistCacheStore: Writable<{
|
||||
}> = writable({});
|
||||
|
||||
export const isAndroidTvStore: Writable<boolean> = writable(false);
|
||||
export const isTvRouteLoading: Writable<boolean> = writable(false);
|
||||
|
||||
@@ -10,6 +10,7 @@ import { loadEntirePlaylist } from '$lib/playlist';
|
||||
import {
|
||||
authStore,
|
||||
playerProxyVideosStore,
|
||||
playerState,
|
||||
returnYTDislikesInstanceStore,
|
||||
returnYtDislikesStore
|
||||
} from '$lib/store';
|
||||
@@ -19,11 +20,18 @@ import { get } from 'svelte/store';
|
||||
import { _ } from './i18n';
|
||||
|
||||
export async function getWatchDetails(videoId: string, url: URL) {
|
||||
const playerStateRetrieved = get(playerState);
|
||||
|
||||
let video;
|
||||
try {
|
||||
video = await getVideo(videoId, get(playerProxyVideosStore), { priority: 'high' });
|
||||
} catch (errorMessage: any) {
|
||||
error(500, errorMessage);
|
||||
|
||||
if (playerStateRetrieved && playerStateRetrieved.data.video.videoId === videoId) {
|
||||
video = playerStateRetrieved.data.video;
|
||||
} else {
|
||||
try {
|
||||
video = await getVideo(videoId, get(playerProxyVideosStore), { priority: 'high' });
|
||||
} catch (errorMessage: any) {
|
||||
error(500, errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
if (video.premium) {
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
import Settings from '$lib/components/settings/Settings.svelte';
|
||||
import SyncParty from '$lib/components/SyncParty.svelte';
|
||||
import Thumbnail from '$lib/components/Thumbnail.svelte';
|
||||
import Player from '$lib/components/Player.svelte';
|
||||
import '$lib/css/global.css';
|
||||
import { bookmarkletLoadFromUrl, loadSettingsFromEnv } from '$lib/externalSettings';
|
||||
import { getPages } from '$lib/navPages';
|
||||
@@ -23,6 +24,8 @@
|
||||
interfaceAmoledTheme,
|
||||
interfaceDefaultPage,
|
||||
isAndroidTvStore,
|
||||
playerState,
|
||||
playertheatreModeIsActive,
|
||||
playlistCacheStore,
|
||||
searchCacheStore,
|
||||
syncPartyPeerStore,
|
||||
@@ -40,6 +43,9 @@
|
||||
import { get } from 'svelte/store';
|
||||
import { pwaInfo } from 'virtual:pwa-info';
|
||||
import Mousetrap from 'mousetrap';
|
||||
import { truncate } from '$lib/misc';
|
||||
import Author from '$lib/components/watch/Author.svelte';
|
||||
import LikesDislikes from '$lib/components/watch/LikesDislikes.svelte';
|
||||
|
||||
let { children } = $props();
|
||||
|
||||
@@ -52,6 +58,12 @@
|
||||
|
||||
let notifications: Notification[] = $state([]);
|
||||
|
||||
let playerIsPip: boolean = $state(false);
|
||||
|
||||
page.subscribe((pageData) => {
|
||||
playerIsPip = !pageData.url.pathname.includes('/watch');
|
||||
});
|
||||
|
||||
interfaceAmoledTheme.subscribe(async () => {
|
||||
setAmoledTheme();
|
||||
|
||||
@@ -383,6 +395,42 @@
|
||||
</dialog>
|
||||
|
||||
<main id="main-content" class="responsive max root" tabindex="0" role="region">
|
||||
{#if $playerState}
|
||||
<div class="grid">
|
||||
<div
|
||||
class:pip={playerIsPip}
|
||||
class:s12={!playerIsPip}
|
||||
class:m12={!playerIsPip}
|
||||
class:l12={$playertheatreModeIsActive && !playerIsPip}
|
||||
class:l9={!$playertheatreModeIsActive && !playerIsPip}
|
||||
>
|
||||
{#if playerIsPip}
|
||||
<nav>
|
||||
<h6 class="max">{truncate($playerState.data.video.title, 20)}</h6>
|
||||
<button class="border" onclick={() => playerState.set(undefined)}>
|
||||
<i>close</i>
|
||||
</button>
|
||||
</nav>
|
||||
<div class="space"></div>
|
||||
{/if}
|
||||
<div style="display: flex;justify-content: center;">
|
||||
{#key $playerState.data.video.videoId}
|
||||
<Player data={$playerState.data} isSyncing={$playerState.isSyncing} />
|
||||
{/key}
|
||||
</div>
|
||||
{#if playerIsPip}
|
||||
<nav>
|
||||
<Author video={$playerState.data.video} hideSubscribe={true} />
|
||||
<div class="max"></div>
|
||||
<a class="button border" href={`/watch/${$playerState.data.video.videoId}`}
|
||||
><i>keyboard_arrow_right</i></a
|
||||
>
|
||||
</nav>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if $navigating}
|
||||
<PageLoading />
|
||||
{:else}
|
||||
@@ -425,4 +473,23 @@
|
||||
min-inline-size: 0.5rem;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.pip {
|
||||
position: fixed;
|
||||
bottom: 10px;
|
||||
right: 0;
|
||||
width: 400px;
|
||||
z-index: 99999;
|
||||
padding: 1em;
|
||||
border: 0.0625rem solid var(--outline-variant);
|
||||
background-color: var(--surface-container-low);
|
||||
color: var(--on-surface);
|
||||
border-radius: 0.75rem;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 993px) {
|
||||
.pip {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
<script lang="ts">
|
||||
import { amSubscribed, deleteUnsubscribe, getChannelContent, postSubscribe } from '$lib/api';
|
||||
import {
|
||||
amSubscribed,
|
||||
deleteUnsubscribe,
|
||||
getChannelContent,
|
||||
postSubscribe,
|
||||
searchChannelContent,
|
||||
type channelContentTypes,
|
||||
type channelSortBy
|
||||
} from '$lib/api';
|
||||
import type { ChannelContentPlaylists, ChannelContentVideos } from '$lib/api/model';
|
||||
import ContentColumn from '$lib/components/ContentColumn.svelte';
|
||||
import PageLoading from '$lib/components/PageLoading.svelte';
|
||||
import PlaylistThumbnail from '$lib/components/PlaylistThumbnail.svelte';
|
||||
import { getBestThumbnail, proxyGoogleImage } from '$lib/images';
|
||||
import { cleanNumber } from '$lib/numbers';
|
||||
import { authStore, interfaceLowBandwidthMode, isAndroidTvStore } from '$lib/store';
|
||||
@@ -19,11 +25,21 @@
|
||||
|
||||
let isSubscribed = $state(false);
|
||||
|
||||
let tab: 'videos' | 'playlists' | 'streams' | 'shorts' = $state('videos');
|
||||
let tab: channelContentTypes = $state('videos');
|
||||
|
||||
let sortBy: channelSortBy = $state('newest');
|
||||
const sortByOptions: channelSortBy[] = ['newest', 'oldest', 'popular'];
|
||||
|
||||
let showSearch: boolean = $state(false);
|
||||
let channelSearch: string = $state('');
|
||||
|
||||
let displayContent: ChannelContentPlaylists | ChannelContentVideos | undefined =
|
||||
$state(undefined);
|
||||
|
||||
async function searchChannel() {
|
||||
displayContent = await searchChannelContent(data.channel.authorId, channelSearch);
|
||||
}
|
||||
|
||||
async function loadMore(event: InfiniteEvent) {
|
||||
if (typeof displayContent === 'undefined') return;
|
||||
|
||||
@@ -34,7 +50,8 @@
|
||||
|
||||
const newContent = await getChannelContent(data.channel.authorId, {
|
||||
type: tab,
|
||||
continuation: displayContent.continuation
|
||||
continuation: displayContent.continuation,
|
||||
sortBy: sortBy
|
||||
});
|
||||
if ('videos' in newContent && 'videos' in displayContent) {
|
||||
if (displayContent.continuation === newContent.continuation) {
|
||||
@@ -62,7 +79,10 @@
|
||||
|
||||
let channelPfp: string | undefined = $state();
|
||||
onMount(async () => {
|
||||
displayContent = await getChannelContent(data.channel.authorId, { type: 'videos' });
|
||||
displayContent = await getChannelContent(data.channel.authorId, {
|
||||
type: 'videos',
|
||||
sortBy: sortBy
|
||||
});
|
||||
|
||||
if (!get(interfaceLowBandwidthMode)) {
|
||||
const channelPfpResp = await fetch(
|
||||
@@ -201,6 +221,46 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<div class="s12 m6 l6">
|
||||
<nav class="group">
|
||||
{#each sortByOptions as sortingOption}
|
||||
<button
|
||||
class="no-round"
|
||||
onclick={async () => {
|
||||
sortBy = sortingOption;
|
||||
|
||||
displayContent = await getChannelContent(data.channel.authorId, {
|
||||
type: tab,
|
||||
sortBy: sortBy
|
||||
});
|
||||
}}
|
||||
class:active={sortBy === sortingOption}>{$_(sortingOption)}</button
|
||||
>
|
||||
{/each}
|
||||
</nav>
|
||||
</div>
|
||||
<div class="s12 m6 l6">
|
||||
{#if showSearch}
|
||||
<div class="max field suffix prefix small no-margin surface-variant">
|
||||
<i class="front">search</i><input
|
||||
bind:value={channelSearch}
|
||||
oninput={searchChannel}
|
||||
type="text"
|
||||
placeholder={$_('searchPlaceholder')}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<nav class="right-align m l">
|
||||
<button onclick={() => (showSearch = true)}><i>search</i></button>
|
||||
</nav>
|
||||
<button class="s" onclick={() => (showSearch = true)}><i>search</i></button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space"></div>
|
||||
|
||||
{#if displayContent}
|
||||
{#if 'videos' in displayContent}
|
||||
<ItemsList items={displayContent.videos} />
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { getSearch, type SearchOptions } from '$lib/api';
|
||||
import PageLoading from '$lib/components/PageLoading.svelte';
|
||||
import { feedLastItemId, searchCacheStore } from '$lib/store';
|
||||
import { onMount } from 'svelte';
|
||||
import { searchCacheStore } from '$lib/store';
|
||||
import { _ } from '$lib/i18n';
|
||||
import InfiniteLoading, { type InfiniteEvent } from 'svelte-infinite-loading';
|
||||
import ItemsList from '$lib/components/ItemsList.svelte';
|
||||
@@ -63,16 +62,6 @@
|
||||
}
|
||||
]);
|
||||
|
||||
onMount(() => {
|
||||
if ($feedLastItemId) {
|
||||
document
|
||||
.getElementById($feedLastItemId)
|
||||
?.scrollIntoView({ behavior: 'instant', block: 'center', inline: 'nearest' });
|
||||
|
||||
feedLastItemId.set(undefined);
|
||||
}
|
||||
});
|
||||
|
||||
async function updateSearch() {
|
||||
data.searchStoreId = JSON.stringify(searchOptions) + data.slug;
|
||||
searchCacheStore.set({ [data.searchStoreId]: await getSearch(data.slug, searchOptions) });
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
|
||||
<div class="space"></div>
|
||||
|
||||
<div class="max field round suffix prefix small no-margin surface-variant">
|
||||
<div class="max field suffix prefix small no-margin surface-variant">
|
||||
<i class="front">search</i><input
|
||||
bind:value={search}
|
||||
oninput={searchSubs}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
removePlaylistVideo
|
||||
} from '$lib/api/index';
|
||||
import type { Comments, PlaylistPage } from '$lib/api/model';
|
||||
import Player from '$lib/components/Player.svelte';
|
||||
import ShareVideo from '$lib/components/ShareVideo.svelte';
|
||||
import Thumbnail from '$lib/components/Thumbnail.svelte';
|
||||
import Transcript from '$lib/components/Transcript.svelte';
|
||||
@@ -18,8 +17,11 @@
|
||||
authStore,
|
||||
interfaceAutoExpandChapters,
|
||||
interfaceAutoExpandComments,
|
||||
playerMiniplayerEnabled,
|
||||
playerPlaylistHistory,
|
||||
playerState,
|
||||
playerTheatreModeByDefaultStore,
|
||||
playertheatreModeIsActive,
|
||||
playlistCacheStore,
|
||||
playlistSettingsStore,
|
||||
syncPartyConnectionsStore,
|
||||
@@ -27,7 +29,6 @@
|
||||
} from '$lib/store';
|
||||
import ui from 'beercss';
|
||||
import type { DataConnection } from 'peerjs';
|
||||
import { type Segment } from 'sponsorblock-api';
|
||||
import { onDestroy, onMount, tick } from 'svelte';
|
||||
import { _ } from '$lib/i18n';
|
||||
import { get } from 'svelte/store';
|
||||
@@ -59,9 +60,7 @@
|
||||
let loopPlaylist: boolean = $state(false);
|
||||
let shufflePlaylist: boolean = $state(false);
|
||||
|
||||
let theatreMode = $state(get(playerTheatreModeByDefaultStore));
|
||||
|
||||
let segments: Segment[] = $state([]);
|
||||
playertheatreModeIsActive.set(get(playerTheatreModeByDefaultStore));
|
||||
|
||||
let pauseTimerSeconds: number = $state(-1);
|
||||
|
||||
@@ -72,6 +71,13 @@
|
||||
let premiereTime = $state('');
|
||||
let premiereUpdateInterval: NodeJS.Timeout;
|
||||
|
||||
if (!data.video.premiereTimestamp) {
|
||||
playerState.set({
|
||||
data: data,
|
||||
isSyncing: $syncPartyPeerStore !== null
|
||||
});
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if ($interfaceAutoExpandComments && comments) {
|
||||
expandSummery('comment-section');
|
||||
@@ -217,41 +223,53 @@
|
||||
playerSyncEvents(connections[connections.length - 1]);
|
||||
});
|
||||
|
||||
onMount(async () => {
|
||||
if (data.playlistId) {
|
||||
await goToCurrentPlaylistItem();
|
||||
playerPlaylistHistory.set([data.video.videoId, ...$playerPlaylistHistory]);
|
||||
}
|
||||
onMount(() => {
|
||||
// Required due to needing the playerElement
|
||||
let loadedPlayer = false;
|
||||
playerState.subscribe(async (updatedPlayerState) => {
|
||||
if (!updatedPlayerState?.playerElement || loadedPlayer) {
|
||||
return;
|
||||
}
|
||||
loadedPlayer = true;
|
||||
|
||||
if ($interfaceAutoExpandChapters) {
|
||||
expandSummery('chapter-section');
|
||||
}
|
||||
playerElement = updatedPlayerState.playerElement;
|
||||
|
||||
if ($syncPartyConnectionsStore) {
|
||||
$syncPartyConnectionsStore.forEach((conn) => {
|
||||
playerSyncEvents(conn);
|
||||
});
|
||||
}
|
||||
if (data.playlistId) {
|
||||
await goToCurrentPlaylistItem();
|
||||
playerPlaylistHistory.set([data.video.videoId, ...$playerPlaylistHistory]);
|
||||
}
|
||||
|
||||
if (playerElement) {
|
||||
playerElement.addEventListener('timeupdate', () => {
|
||||
if (!playerElement) return;
|
||||
playerCurrentTime = playerElement.currentTime;
|
||||
});
|
||||
}
|
||||
if ($interfaceAutoExpandChapters) {
|
||||
expandSummery('chapter-section');
|
||||
}
|
||||
|
||||
if (data.video.premiereTimestamp) {
|
||||
premiereTime = humanFriendlyTimestamp(data.video.premiereTimestamp);
|
||||
premiereUpdateInterval = setInterval(async () => {
|
||||
data = await getWatchDetails(data.video.videoId, page.url);
|
||||
if ($syncPartyConnectionsStore) {
|
||||
$syncPartyConnectionsStore.forEach((conn) => {
|
||||
playerSyncEvents(conn);
|
||||
});
|
||||
}
|
||||
|
||||
if (data.video.premiereTimestamp) {
|
||||
premiereTime = humanFriendlyTimestamp(data.video.premiereTimestamp);
|
||||
} else {
|
||||
clearInterval(premiereUpdateInterval);
|
||||
}
|
||||
}, 60000);
|
||||
}
|
||||
if (playerElement) {
|
||||
playerElement.addEventListener('timeupdate', () => {
|
||||
if (!playerElement) return;
|
||||
playerCurrentTime = playerElement.currentTime;
|
||||
});
|
||||
}
|
||||
|
||||
if (data.video.premiereTimestamp) {
|
||||
premiereTime = humanFriendlyTimestamp(data.video.premiereTimestamp);
|
||||
premiereUpdateInterval = setInterval(async () => {
|
||||
data = await getWatchDetails(data.video.videoId, page.url);
|
||||
|
||||
if (data.video.premiereTimestamp) {
|
||||
premiereTime = humanFriendlyTimestamp(data.video.premiereTimestamp);
|
||||
} else {
|
||||
clearInterval(premiereUpdateInterval);
|
||||
playerState.set({ ...$playerState, data: { ...data } });
|
||||
}
|
||||
}, 60000);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
@@ -265,6 +283,16 @@
|
||||
if (premiereUpdateInterval) {
|
||||
clearInterval(premiereUpdateInterval);
|
||||
}
|
||||
|
||||
if (
|
||||
playerElement?.paused ||
|
||||
playerElement?.ended ||
|
||||
playerElement?.currentTime === 0 ||
|
||||
playerElement?.readyState === 2 ||
|
||||
!playerMiniplayerEnabled
|
||||
) {
|
||||
playerState.set(undefined);
|
||||
}
|
||||
});
|
||||
|
||||
async function goToCurrentPlaylistItem() {
|
||||
@@ -319,7 +347,7 @@
|
||||
}
|
||||
|
||||
function toggleTheatreMode() {
|
||||
theatreMode = !theatreMode;
|
||||
playertheatreModeIsActive.set(!$playertheatreModeIsActive);
|
||||
}
|
||||
|
||||
let pauseTimeout: NodeJS.Timeout | undefined = $state();
|
||||
@@ -341,19 +369,10 @@
|
||||
<title>{data.video.title}</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="grid">
|
||||
<div class={`s12 m12 l${theatreMode ? '12' : '9'}`}>
|
||||
<div class="grid no-padding">
|
||||
<div class={`s12 m12 l${$playertheatreModeIsActive ? '12' : '9'}`}>
|
||||
<div style="display: flex;justify-content: center;">
|
||||
{#if !data.video.premiereTimestamp}
|
||||
{#key data.video.videoId}
|
||||
<Player
|
||||
bind:playerElement
|
||||
bind:segments
|
||||
{data}
|
||||
isSyncing={$syncPartyPeerStore !== null}
|
||||
/>
|
||||
{/key}
|
||||
{:else}
|
||||
{#if data.video.premiereTimestamp}
|
||||
<article class="video-placeholder">
|
||||
<p>{$_('player.premiere')}</p>
|
||||
<h6 class="no-margin no-padding">
|
||||
@@ -373,7 +392,11 @@
|
||||
<div>
|
||||
<LikesDislikes video={data.video} returnYTDislikes={data.streamed.returnYTDislikes} />
|
||||
|
||||
<button onclick={toggleTheatreMode} class="m l" class:border={!theatreMode}>
|
||||
<button
|
||||
onclick={toggleTheatreMode}
|
||||
class="m l"
|
||||
class:border={!$playertheatreModeIsActive}
|
||||
>
|
||||
<i>width_wide</i>
|
||||
<div class="tooltip">{$_('player.theatreMode')}</div>
|
||||
</button>
|
||||
@@ -392,7 +415,10 @@
|
||||
</button>
|
||||
{/if}
|
||||
<button
|
||||
onclick={() => ((showTranscript = !showTranscript), (theatreMode = false))}
|
||||
onclick={() => (
|
||||
(showTranscript = !showTranscript),
|
||||
playertheatreModeIsActive.set(false)
|
||||
)}
|
||||
class:border={!showTranscript}
|
||||
>
|
||||
<i>description</i>
|
||||
@@ -522,8 +548,8 @@
|
||||
</article>
|
||||
{/if}
|
||||
</div>
|
||||
{#if !theatreMode}
|
||||
<div class="s12 m12 l3">
|
||||
{#if !$playertheatreModeIsActive}
|
||||
<div class="s12 m12 l3 recommended">
|
||||
{#if showTranscript && playerElement}
|
||||
<Transcript video={data.video} bind:playerElement />
|
||||
{/if}
|
||||
@@ -671,6 +697,10 @@
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.recommended {
|
||||
margin-top: calc(var(--video-player-height) * -1);
|
||||
}
|
||||
|
||||
.video-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -684,9 +714,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1646px) {
|
||||
.grid {
|
||||
padding: 0;
|
||||
@media only screen and (max-width: 993px) {
|
||||
.recommended {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { navigating } from '$app/stores';
|
||||
import PageLoading from '$lib/components/PageLoading.svelte';
|
||||
import { isTvRouteLoading } from '$lib/store';
|
||||
|
||||
let { children } = $props();
|
||||
</script>
|
||||
@@ -15,7 +14,7 @@
|
||||
</style>
|
||||
</svelte:head>
|
||||
|
||||
{#if $isTvRouteLoading || $navigating}
|
||||
{#if $navigating}
|
||||
<PageLoading />
|
||||
{:else}
|
||||
{@render children?.()}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import { isTvRouteLoading } from '$lib/store';
|
||||
import { getWatchDetails } from '$lib/watch';
|
||||
|
||||
export async function load({ params, url }) {
|
||||
isTvRouteLoading.set(true);
|
||||
const details = await getWatchDetails(params.slug, url);
|
||||
isTvRouteLoading.set(false);
|
||||
return details;
|
||||
return await getWatchDetails(params.slug, url);
|
||||
}
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ import os
|
||||
import re
|
||||
from datetime import datetime
|
||||
|
||||
LATEST_VERSION = "1.11.2"
|
||||
LATEST_VERSION = "1.11.3"
|
||||
RELEASE_DATE = datetime.now().strftime("%Y-%-m-%d") # Format: YYYY-M-D
|
||||
|
||||
WORKING_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "materialious")
|
||||
|
||||
Reference in New Issue
Block a user