+1
-91
@@ -273,94 +273,4 @@ Add these additional environment variables to Materialious.
|
||||
VITE_DEFAULT_PEERJS_HOST: "peerjs.example.com"
|
||||
VITE_DEFAULT_PEERJS_PATH: "/"
|
||||
VITE_DEFAULT_PEERJS_PORT: 443
|
||||
```
|
||||
|
||||
|
||||
## Step 7 (Optional): Enabling downloads
|
||||
|
||||
### Step 1: Add the following to your reverse proxies
|
||||
|
||||
#### Caddy
|
||||
|
||||
```caddy
|
||||
# Materialious
|
||||
materialious.example.com {
|
||||
reverse_proxy localhost:3001
|
||||
|
||||
header {
|
||||
Cross-Origin-Opener-Policy "same-origin"
|
||||
Cross-Origin-Embedder-Policy "require-corp"
|
||||
}
|
||||
}
|
||||
|
||||
# Invidious
|
||||
invidious.example.com {
|
||||
# Other configurations
|
||||
|
||||
header /ggpht/* Cross-Origin-Resource-Policy cross-origin
|
||||
}
|
||||
```
|
||||
|
||||
#### Nginx
|
||||
```nginx
|
||||
# Materialious
|
||||
server {
|
||||
listen 80;
|
||||
server_name materialious.example.com;
|
||||
|
||||
add_header Cross-Origin-Opener-Policy "same-origin" always;
|
||||
add_header Cross-Origin-Embedder-Policy "require-corp" always;
|
||||
|
||||
location / {
|
||||
proxy_pass http://localhost:3001;
|
||||
}
|
||||
}
|
||||
|
||||
# Invidious
|
||||
server {
|
||||
listen 80;
|
||||
server_name invidious.example.com;
|
||||
|
||||
location /ggpht/ {
|
||||
add_header Cross-Origin-Resource-Policy cross-origin;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Traefik
|
||||
Add the following to Materialious
|
||||
|
||||
```yaml
|
||||
# Materialious
|
||||
http:
|
||||
middlewares:
|
||||
materialious-downloads:
|
||||
headers:
|
||||
customResponseHeaders:
|
||||
Cross-Origin-Opener-Policy: "same-origin"
|
||||
Cross-Origin-Embedder-Policy: "require-corp"
|
||||
|
||||
# Invidious
|
||||
http:
|
||||
routers:
|
||||
ggpht-router:
|
||||
rule: "PathPrefix(`/ggpht/`)"
|
||||
service: ggpht-service
|
||||
middlewares:
|
||||
- headers-ggpht
|
||||
|
||||
middlewares:
|
||||
headers-ggpht:
|
||||
headers:
|
||||
customResponseHeaders:
|
||||
Cross-Origin-Resource-Policy: cross-origin
|
||||
```
|
||||
|
||||
|
||||
### Step 2: Modify docker compose.
|
||||
|
||||
Add this environment variable to Materialious.
|
||||
```yaml
|
||||
VITE_DEFAULT_DOWNLOAD_ENABLED: true
|
||||
```
|
||||
|
||||
```
|
||||
@@ -7,8 +7,8 @@ android {
|
||||
applicationId "us.materialio.app"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 98
|
||||
versionName "1.7.16"
|
||||
versionCode 99
|
||||
versionName "1.7.17"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
aaptOptions {
|
||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||
|
||||
@@ -59,7 +59,11 @@
|
||||
|
||||
|
||||
|
||||
<release version="1.7.16" date="2025-4-02">
|
||||
|
||||
<release version="1.7.17" date="2025-4-02">
|
||||
<url>https://github.com/Materialious/Materialious/releases/tag/1.7.17</url>
|
||||
</release>
|
||||
<release version="1.7.16" date="2025-4-02">
|
||||
<url>https://github.com/Materialious/Materialious/releases/tag/1.7.16</url>
|
||||
</release>
|
||||
<release version="1.7.15" date="2025-2-23">
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "Materialious",
|
||||
"version": "1.7.16",
|
||||
"version": "1.7.17",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "Materialious",
|
||||
"version": "1.7.16",
|
||||
"version": "1.7.17",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@capacitor-community/electron": "^5.0.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Materialious",
|
||||
"version": "1.7.16",
|
||||
"version": "1.7.17",
|
||||
"description": "Modern material design for Invidious.",
|
||||
"author": {
|
||||
"name": "Ward Pearce",
|
||||
@@ -44,4 +44,4 @@
|
||||
"capacitor",
|
||||
"electron"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "materialious",
|
||||
"version": "1.7.16",
|
||||
"version": "1.7.17",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
<script lang="ts">
|
||||
import Comment_1 from './Comment.svelte';
|
||||
import { getComments } from '$lib/api';
|
||||
import { type Comment, type Comments } from '$lib/api/model';
|
||||
import { getBestThumbnail, proxyGoogleImage } from '$lib/images';
|
||||
import { interfaceLowBandwidthMode } from '$lib/store';
|
||||
import { numberWithCommas } from '$lib/time';
|
||||
import { _ } from 'svelte-i18n';
|
||||
import CommentSelf from './Comment.svelte';
|
||||
|
||||
interface Props {
|
||||
comment: Comment;
|
||||
@@ -16,6 +15,8 @@
|
||||
|
||||
let replies: Comments | undefined = $state(undefined);
|
||||
|
||||
const replyText: string = comment.replies?.replyCount > 1 ? 'replies' : 'reply';
|
||||
|
||||
async function loadReplies(continuation: string) {
|
||||
try {
|
||||
replies = await getComments(videoId, {
|
||||
@@ -39,91 +40,96 @@
|
||||
|
||||
<div class="comment">
|
||||
{#if !$interfaceLowBandwidthMode}
|
||||
<img
|
||||
class="circle small"
|
||||
src={proxyGoogleImage(getBestThumbnail(comment.authorThumbnails))}
|
||||
alt="comment profile"
|
||||
/>
|
||||
{/if}
|
||||
<div>
|
||||
<div class="row">
|
||||
<a href={`/channel/${comment.authorId}`}>
|
||||
<p class="no-margin">
|
||||
<span class="bold" class:channel-owner={comment.authorIsChannelOwner}
|
||||
>{comment.author}</span
|
||||
>
|
||||
<div class="comment-header">
|
||||
<img
|
||||
class="circle small"
|
||||
src={proxyGoogleImage(getBestThumbnail(comment.authorThumbnails))}
|
||||
alt="comment profile"
|
||||
/>
|
||||
<div class="comment-info">
|
||||
<a href={`/channel/${comment.authorId}`} class="author">
|
||||
<span class="bold" class:channel-owner={comment.authorIsChannelOwner}>
|
||||
{comment.author}
|
||||
</span>
|
||||
<span class="secondary-text">{comment.publishedText}</span>
|
||||
</a>
|
||||
<p class="no-margin">
|
||||
{@html commentTimestamps(comment.contentHtml)}
|
||||
</p>
|
||||
</a>
|
||||
{#if comment.isPinned}
|
||||
<i>push_pin</i>
|
||||
{/if}
|
||||
{#if comment.isEdited}
|
||||
<i>edit</i>
|
||||
{/if}
|
||||
</div>
|
||||
<p class="no-margin">
|
||||
{@html commentTimestamps(comment.contentHtml)}
|
||||
</p>
|
||||
<div style="display: flex;">
|
||||
<p><i>thumb_up</i> {numberWithCommas(comment.likeCount)}</p>
|
||||
{#if comment.creatorHeart}
|
||||
<div>
|
||||
<img
|
||||
class="circle"
|
||||
style="width: 25px; height: 25px"
|
||||
src={proxyGoogleImage(comment.creatorHeart.creatorThumbnail)}
|
||||
alt="Creator profile"
|
||||
/>
|
||||
<i style="font-size: 20px;margin-left: 5px;" class="absolute left red-text bottom fill"
|
||||
>favorite</i
|
||||
>
|
||||
<div class="comment-actions">
|
||||
<p class="no-margin no-padding"><i>thumb_up</i> {numberWithCommas(comment.likeCount)}</p>
|
||||
{#if comment.replies && !replies}
|
||||
<button
|
||||
onclick={async () => loadReplies(comment.replies.continuation)}
|
||||
class="transparent replies"
|
||||
>
|
||||
<i class="white-text">expand_more</i>
|
||||
<span class="white-text">{comment.replies.replyCount} {replyText}</span>
|
||||
</button>
|
||||
{:else if replies}
|
||||
<button onclick={() => (replies = undefined)} class="transparent replies">
|
||||
<i class="white-text">expand_less</i>
|
||||
<span class="white-text">Hide {replyText}</span>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if replies}
|
||||
{#if replies}
|
||||
<div style="margin-left: 5em;">
|
||||
{#each replies.comments as reply}
|
||||
<Comment_1 comment={reply} {videoId} />
|
||||
<CommentSelf comment={reply} {videoId} />
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
{#if comment.replies && !replies}
|
||||
<button
|
||||
onclick={async () => loadReplies(comment.replies.continuation)}
|
||||
class="transparent replies"
|
||||
>
|
||||
<i class="primary-text">expand_more</i>
|
||||
<span class="primary-text">{comment.replies.replyCount} {$_('replies')}</span>
|
||||
</button>
|
||||
{:else if replies}
|
||||
<button onclick={() => (replies = undefined)} class="transparent replies">
|
||||
<i class="primary-text">expand_less</i>
|
||||
<span class="primary-text">{$_('hideReplies')}</span>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.comment img {
|
||||
margin: 0.5em 1em 0 1em;
|
||||
}
|
||||
|
||||
.comment {
|
||||
display: flex;
|
||||
margin-bottom: 0.8em;
|
||||
flex-direction: column;
|
||||
margin-bottom: 1em;
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
.channel-owner {
|
||||
background-color: var(--primary);
|
||||
padding: 0 0.5em;
|
||||
border-radius: 1em;
|
||||
color: var(--surface-variant);
|
||||
.comment-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.comment-header img {
|
||||
margin: 0;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.comment-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.author {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 6px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.comment-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
margin-top: 5px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
a {
|
||||
justify-content: start;
|
||||
}
|
||||
|
||||
.replies {
|
||||
margin-left: 0;
|
||||
padding: 0.1em;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import '$lib/css/shaka-player-theme.css';
|
||||
import { getBestThumbnail } from '$lib/images';
|
||||
import { padTime, videoLength } from '$lib/time';
|
||||
import { Capacitor } from '@capacitor/core';
|
||||
@@ -60,6 +61,51 @@
|
||||
let player: shaka.Player;
|
||||
let shakaUi: shaka.ui.Overlay;
|
||||
|
||||
const STORAGE_KEY_QUALITY = 'shaka-preferred-quality';
|
||||
const STORAGE_KEY_VOLUME = 'shaka-preferred-volume';
|
||||
|
||||
function saveQualityPreference() {
|
||||
const tracks = player.getVariantTracks();
|
||||
const selectedTrack = tracks.find((track) => track.active);
|
||||
if (selectedTrack) {
|
||||
localStorage.setItem(STORAGE_KEY_QUALITY, selectedTrack.bandwidth.toString());
|
||||
}
|
||||
}
|
||||
|
||||
function saveVolumePreference() {
|
||||
localStorage.setItem(STORAGE_KEY_VOLUME, playerElement.volume.toString());
|
||||
}
|
||||
|
||||
function restorePreferences() {
|
||||
const savedQuality =
|
||||
localStorage.getItem(STORAGE_KEY_QUALITY) ??
|
||||
(import.meta.env.VITE_DEFAULT_DASH_BITRATE as string);
|
||||
|
||||
if (savedQuality) {
|
||||
const qualityBandwidth = parseInt(savedQuality, 10);
|
||||
const tracks = player.getVariantTracks();
|
||||
|
||||
let preferredTrack = tracks.find((track) => track.bandwidth === qualityBandwidth);
|
||||
if (!preferredTrack) {
|
||||
preferredTrack = tracks.reduce((prev, curr) =>
|
||||
Math.abs(curr.bandwidth - qualityBandwidth) < Math.abs(prev.bandwidth - qualityBandwidth)
|
||||
? curr
|
||||
: prev
|
||||
);
|
||||
}
|
||||
|
||||
if (preferredTrack) {
|
||||
player.selectVariantTrack(preferredTrack, true);
|
||||
player.configure({ abr: { enabled: false } });
|
||||
}
|
||||
}
|
||||
|
||||
const savedVolume = localStorage.getItem(STORAGE_KEY_VOLUME);
|
||||
if (savedVolume) {
|
||||
playerElement.volume = parseFloat(savedVolume);
|
||||
}
|
||||
}
|
||||
|
||||
function loadTimeFromUrl(page: Page): boolean {
|
||||
if (player) {
|
||||
const timeGivenUrl = page.url.searchParams.get('time');
|
||||
@@ -92,6 +138,9 @@
|
||||
playerElement
|
||||
);
|
||||
|
||||
player.addEventListener('adaptation', () => setTimeout(saveQualityPreference, 10000));
|
||||
playerElement.addEventListener('volumechange', saveVolumePreference);
|
||||
|
||||
shakaUi.configure({
|
||||
controlPanelElements: [
|
||||
'play_pause',
|
||||
@@ -194,56 +243,73 @@
|
||||
};
|
||||
|
||||
if (type == shaka.net.NetworkingEngine.RequestType.SEGMENT) {
|
||||
const googUmp = new GoogleVideo.UMP(
|
||||
new GoogleVideo.ChunkedDataBuffer([new Uint8Array(response.data as ArrayBuffer)])
|
||||
);
|
||||
const url = new URL(response.uri);
|
||||
|
||||
let redirect: Protos.SabrRedirect | undefined;
|
||||
// Fix positioning for auto-generated subtitles
|
||||
if (
|
||||
url.hostname.endsWith('.youtube.com') &&
|
||||
url.pathname === '/api/timedtext' &&
|
||||
url.searchParams.get('caps') === 'asr' &&
|
||||
url.searchParams.get('kind') === 'asr' &&
|
||||
url.searchParams.get('fmt') === 'vtt'
|
||||
) {
|
||||
const stringBody = new TextDecoder().decode(response.data);
|
||||
// position:0% for LTR text and position:100% for RTL text
|
||||
const cleaned = stringBody.replaceAll(/ align:start position:(?:10)?0%$/gm, '');
|
||||
|
||||
googUmp.parse((part) => {
|
||||
try {
|
||||
const data = part.data.chunks[0];
|
||||
switch (part.type) {
|
||||
case 20: {
|
||||
const mediaHeader = Protos.MediaHeader.decode(data);
|
||||
console.info('[MediaHeader]:', mediaHeader);
|
||||
break;
|
||||
}
|
||||
case 21: {
|
||||
handleMediaData(part.data.split(1).remainingBuffer.chunks[0]);
|
||||
break;
|
||||
}
|
||||
case 43: {
|
||||
redirect = Protos.SabrRedirect.decode(data);
|
||||
console.info('[SABRRedirect]:', redirect);
|
||||
break;
|
||||
}
|
||||
case 58: {
|
||||
const streamProtectionStatus = Protos.StreamProtectionStatus.decode(data);
|
||||
switch (streamProtectionStatus.status) {
|
||||
case 1:
|
||||
console.info('[StreamProtectionStatus]: Ok');
|
||||
break;
|
||||
case 2:
|
||||
console.error('[StreamProtectionStatus]: Attestation pending');
|
||||
break;
|
||||
case 3:
|
||||
console.error('[StreamProtectionStatus]: Attestation required');
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
response.data = new TextEncoder().encode(cleaned).buffer as ArrayBuffer;
|
||||
} else {
|
||||
const googUmp = new GoogleVideo.UMP(
|
||||
new GoogleVideo.ChunkedDataBuffer([new Uint8Array(response.data as ArrayBuffer)])
|
||||
);
|
||||
|
||||
let redirect: Protos.SabrRedirect | undefined;
|
||||
|
||||
googUmp.parse((part) => {
|
||||
try {
|
||||
const data = part.data.chunks[0];
|
||||
switch (part.type) {
|
||||
case 20: {
|
||||
const mediaHeader = Protos.MediaHeader.decode(data);
|
||||
console.info('[MediaHeader]:', mediaHeader);
|
||||
break;
|
||||
}
|
||||
case 21: {
|
||||
handleMediaData(part.data.split(1).remainingBuffer.chunks[0]);
|
||||
break;
|
||||
}
|
||||
case 43: {
|
||||
redirect = Protos.SabrRedirect.decode(data);
|
||||
console.info('[SABRRedirect]:', redirect);
|
||||
break;
|
||||
}
|
||||
case 58: {
|
||||
const streamProtectionStatus = Protos.StreamProtectionStatus.decode(data);
|
||||
switch (streamProtectionStatus.status) {
|
||||
case 1:
|
||||
console.info('[StreamProtectionStatus]: Ok');
|
||||
break;
|
||||
case 2:
|
||||
console.error('[StreamProtectionStatus]: Attestation pending');
|
||||
break;
|
||||
case 3:
|
||||
console.error('[StreamProtectionStatus]: Attestation required');
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('An error occurred while processing the part:', error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('An error occurred while processing the part:', error);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (redirect) return handleRedirect(redirect);
|
||||
if (redirect) return handleRedirect(redirect);
|
||||
|
||||
if (mediaData.length) response.data = mediaData;
|
||||
if (mediaData.length) response.data = mediaData;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -398,6 +464,20 @@
|
||||
snackBarAlert = get(_)('player.youtubeJsFallBack');
|
||||
ui('#snackbar-alert');
|
||||
}
|
||||
|
||||
restorePreferences();
|
||||
|
||||
const overflowMenuButton = document.querySelector('.shaka-overflow-menu-button');
|
||||
if (overflowMenuButton) {
|
||||
overflowMenuButton.innerHTML = 'settings';
|
||||
}
|
||||
|
||||
const backToOverflowButton = document.querySelector(
|
||||
'.shaka-back-to-overflow-button .material-icons-round'
|
||||
);
|
||||
if (backToOverflowButton) {
|
||||
backToOverflowButton.innerHTML = 'arrow_back_ios_new';
|
||||
}
|
||||
});
|
||||
|
||||
async function loadPlayerPos() {
|
||||
@@ -482,6 +562,7 @@
|
||||
|
||||
<div
|
||||
id="shaka-container"
|
||||
class="youtube-theme"
|
||||
style="max-height: 80vh; max-width: calc(80vh * 16 / 9); overflow: hidden; position: relative; flex: 1; background-color: black;"
|
||||
data-shaka-player-container
|
||||
>
|
||||
|
||||
@@ -26,15 +26,17 @@ article, dialog {
|
||||
}
|
||||
|
||||
button.row {
|
||||
padding: 0 !important;
|
||||
background-color: transparent !important;
|
||||
border: none !important;
|
||||
color: var(--on-surface) !important;
|
||||
width: 100%;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
button.row:hover {
|
||||
box-shadow: none !important;
|
||||
background-color: transparent !important;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 650px) {
|
||||
|
||||
@@ -0,0 +1,314 @@
|
||||
.youtube-theme {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
}
|
||||
|
||||
.youtube-theme .shaka-bottom-controls {
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
padding-bottom: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.youtube-theme .shaka-bottom-controls {
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-box-direction: normal;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.youtube-theme .shaka-ad-controls {
|
||||
-webkit-box-ordinal-group: 2;
|
||||
-ms-flex-order: 1;
|
||||
order: 1;
|
||||
}
|
||||
|
||||
.youtube-theme .shaka-controls-button-panel {
|
||||
-webkit-box-ordinal-group: 3;
|
||||
-ms-flex-order: 2;
|
||||
order: 2;
|
||||
height: 40px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.youtube-theme .shaka-range-container {
|
||||
margin: 4px 10px 4px 10px;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.youtube-theme .shaka-small-play-button {
|
||||
-webkit-box-ordinal-group: -2;
|
||||
-ms-flex-order: -3;
|
||||
order: -3;
|
||||
}
|
||||
|
||||
.youtube-theme .shaka-mute-button {
|
||||
-webkit-box-ordinal-group: -1;
|
||||
-ms-flex-order: -2;
|
||||
order: -2;
|
||||
}
|
||||
|
||||
.youtube-theme .shaka-controls-button-panel>* {
|
||||
margin: 0;
|
||||
padding: 3px 8px;
|
||||
color: var(--primary-text);
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.youtube-theme .shaka-controls-button-panel>*:focus {
|
||||
outline: none;
|
||||
-webkit-box-shadow: inset 0 0 0 2px var(--primary);
|
||||
box-shadow: inset 0 0 0 2px var(--primary);
|
||||
color: var(--surface-bright);
|
||||
}
|
||||
|
||||
.youtube-theme .shaka-controls-button-panel>*:hover {
|
||||
color: var(--surface-variant);
|
||||
}
|
||||
|
||||
.youtube-theme .shaka-controls-button-panel .shaka-volume-bar-container {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
left: -1px;
|
||||
-webkit-box-ordinal-group: 0;
|
||||
-ms-flex-order: -1;
|
||||
order: -1;
|
||||
opacity: 0;
|
||||
width: 0px;
|
||||
-webkit-transition: width 0.2s cubic-bezier(0.4, 0, 1, 1);
|
||||
transition: width 0.2s cubic-bezier(0.4, 0, 1, 1);
|
||||
height: 3px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.youtube-theme .shaka-controls-button-panel .shaka-volume-bar-container:hover,
|
||||
.youtube-theme .shaka-controls-button-panel .shaka-volume-bar-container:focus {
|
||||
display: block;
|
||||
width: 50px;
|
||||
opacity: 1;
|
||||
padding: 0 6px;
|
||||
}
|
||||
|
||||
.youtube-theme .shaka-mute-button:hover+div {
|
||||
opacity: 1;
|
||||
width: 50px;
|
||||
padding: 0 6px;
|
||||
}
|
||||
|
||||
.youtube-theme .shaka-current-time {
|
||||
padding: 0 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.youtube-theme .shaka-seek-bar-container {
|
||||
height: 3px;
|
||||
position: relative;
|
||||
top: -1px;
|
||||
border-radius: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.youtube-theme .shaka-seek-bar-container .shaka-range-element {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.youtube-theme .shaka-seek-bar-container:hover {
|
||||
height: 5px;
|
||||
top: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.youtube-theme .shaka-seek-bar-container:hover .shaka-range-element {
|
||||
opacity: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.youtube-theme .shaka-seek-bar-container input[type=range]::-webkit-slider-thumb {
|
||||
background: var(--primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.youtube-theme .shaka-seek-bar-container input[type=range]::-moz-range-thumb {
|
||||
background: var(--primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.youtube-theme .shaka-seek-bar-container input[type=range]::-ms-thumb {
|
||||
background: var(--primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.youtube-theme .shaka-video-container * {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
}
|
||||
|
||||
.youtube-theme .shaka-overflow-menu,
|
||||
.youtube-theme .shaka-settings-menu {
|
||||
border-radius: 2px;
|
||||
background: var(--surface-dim);
|
||||
text-shadow: 0 0 2px rgb(0 0 0%);
|
||||
-webkit-transition: opacity 0.1s cubic-bezier(0, 0, 0.2, 1);
|
||||
transition: opacity 0.1s cubic-bezier(0, 0, 0.2, 1);
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
right: 10px;
|
||||
bottom: 50px;
|
||||
padding: 8px 0;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.youtube-theme .shaka-settings-menu {
|
||||
padding: 0 0 8px 0;
|
||||
}
|
||||
|
||||
.youtube-theme .shaka-settings-menu button {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.youtube-theme .shaka-settings-menu button span {
|
||||
margin-left: 33px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.youtube-theme .shaka-settings-menu button[aria-selected="true"] {
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.youtube-theme .shaka-settings-menu button[aria-selected="true"] span {
|
||||
-webkit-box-ordinal-group: 3;
|
||||
-ms-flex-order: 2;
|
||||
order: 2;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.youtube-theme .shaka-settings-menu button[aria-selected="true"] i {
|
||||
-webkit-box-ordinal-group: 2;
|
||||
-ms-flex-order: 1;
|
||||
order: 1;
|
||||
font-size: 18px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.youtube-theme .shaka-overflow-menu button {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.youtube-theme .shaka-overflow-menu button i {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.youtube-theme .shaka-overflow-menu button .shaka-overflow-button-label {
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-pack: justify;
|
||||
-ms-flex-pack: justify;
|
||||
justify-content: space-between;
|
||||
-webkit-box-orient: horizontal;
|
||||
-webkit-box-direction: normal;
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
cursor: default;
|
||||
outline: none;
|
||||
height: 40px;
|
||||
-webkit-box-flex: 0;
|
||||
-ms-flex: 0 0 100%;
|
||||
flex: 0 0 100%;
|
||||
}
|
||||
|
||||
.youtube-theme .shaka-overflow-menu button .shaka-overflow-button-label span {
|
||||
-ms-flex-negative: initial;
|
||||
flex-shrink: initial;
|
||||
padding-left: 15px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.youtube-theme .shaka-overflow-menu span+span {
|
||||
color: var(--primary-text);
|
||||
font-weight: 400 !important;
|
||||
font-size: 12px !important;
|
||||
padding-right: 8px;
|
||||
padding-left: 0 !important;
|
||||
}
|
||||
|
||||
|
||||
.youtube-theme .shaka-overflow-menu .shaka-pip-button span+span {
|
||||
padding-right: 15px !important;
|
||||
}
|
||||
|
||||
.youtube-theme .shaka-overflow-menu .shaka-pip-button span+span:after {
|
||||
content: "";
|
||||
}
|
||||
|
||||
.youtube-theme .shaka-back-to-overflow-button {
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
|
||||
font-size: 12px;
|
||||
color: var(--primary-text);
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.youtube-theme .shaka-back-to-overflow-button .material-icons-round {
|
||||
font-size: 15px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.youtube-theme .shaka-back-to-overflow-button span {
|
||||
margin-left: 3px !important;
|
||||
}
|
||||
|
||||
.youtube-theme .shaka-overflow-menu button:hover,
|
||||
.youtube-theme .shaka-settings-menu button:hover {
|
||||
background-color: var(--surface-bright);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.youtube-theme .shaka-overflow-menu button:hover label,
|
||||
.youtube-theme .shaka-settings-menu button:hover label {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.youtube-theme .shaka-overflow-menu button:focus,
|
||||
.youtube-theme .shaka-settings-menu button:focus {
|
||||
background-color: var(--surface-bright);
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
.youtube-theme .shaka-overflow-menu button,
|
||||
.youtube-theme .shaka-settings-menu button {
|
||||
color: var(--primary-text);
|
||||
}
|
||||
|
||||
.youtube-theme .shaka-captions-off {
|
||||
color: var(--surface-bright);
|
||||
}
|
||||
|
||||
.youtube-theme .shaka-overflow-menu-button {
|
||||
font-size: 18px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.youtube-theme .shaka-fullscreen-button:hover {
|
||||
font-size: 25px;
|
||||
-webkit-transition: font-size 0.1s cubic-bezier(0, 0, 0.2, 1);
|
||||
transition: font-size 0.1s cubic-bezier(0, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.youtube-theme .shaka-controls-button-panel>*:hover {
|
||||
color: var(--primary) !important;
|
||||
}
|
||||
@@ -239,7 +239,9 @@
|
||||
{/if}
|
||||
|
||||
{#if mobileSearchShow}
|
||||
<Search on:searchCancelled={() => (mobileSearchShow = false)} />
|
||||
<div style="width: 100%;">
|
||||
<Search on:searchCancelled={() => (mobileSearchShow = false)} />
|
||||
</div>
|
||||
{:else}
|
||||
{#if !Capacitor.isNativePlatform()}
|
||||
<button data-ui="#sync-party" class="circle large transparent">
|
||||
|
||||
@@ -89,21 +89,26 @@
|
||||
<span>{$_('player.share.title')}</span>
|
||||
<menu class="no-wrap mobile">
|
||||
{#if !Capacitor.isNativePlatform()}
|
||||
<a
|
||||
href="#share"
|
||||
<button
|
||||
class="row"
|
||||
onclick={async () => {
|
||||
await Clipboard.write({ string: location.href });
|
||||
}}>{$_('player.share.materialiousLink')}</a
|
||||
}}>{$_('player.share.materialiousLink')}</button
|
||||
>
|
||||
{/if}
|
||||
<a
|
||||
href="#share"
|
||||
onclick={async () => {
|
||||
await Clipboard.write({
|
||||
string: `https://www.youtube.com/playlist?list=${data.playlist.playlistId}`
|
||||
});
|
||||
}}>{$_('player.share.youtubeLink')}</a
|
||||
>
|
||||
<!--Silly hack so svelte doesnt error-->
|
||||
{#if true}
|
||||
<button
|
||||
class="row"
|
||||
onclick={async () => {
|
||||
await Clipboard.write({
|
||||
string: `https://www.youtube.com/playlist?list=${data.playlist.playlistId}`
|
||||
});
|
||||
}}
|
||||
>
|
||||
{$_('player.share.youtubeLink')}
|
||||
</button>
|
||||
{/if}
|
||||
</menu>
|
||||
</button>
|
||||
</article>
|
||||
|
||||
@@ -540,8 +540,8 @@
|
||||
<div class="tooltip">{$_('player.addToPlaylist')}</div>
|
||||
<menu class="no-wrap mobile">
|
||||
{#each personalPlaylists as personalPlaylist}
|
||||
<a
|
||||
href="#add"
|
||||
<button
|
||||
class="row"
|
||||
onclick={async () => await toggleVideoToPlaylist(personalPlaylist.playlistId)}
|
||||
>
|
||||
<nav>
|
||||
@@ -554,7 +554,7 @@
|
||||
<i>add</i>
|
||||
{/if}
|
||||
</nav>
|
||||
</a>
|
||||
</button>
|
||||
{/each}
|
||||
</menu>
|
||||
</button>
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ import os
|
||||
import re
|
||||
from datetime import datetime
|
||||
|
||||
LATEST_VERSION = "1.7.16"
|
||||
LATEST_VERSION = "1.7.17"
|
||||
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