Merge pull request #1264 from Materialious/update/1.12.6

Update/1.12.6
This commit is contained in:
Ward
2025-12-21 19:06:23 +00:00
committed by GitHub
19 changed files with 515 additions and 295 deletions
+2 -2
View File
@@ -7,8 +7,8 @@ android {
applicationId "us.materialio.app"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 177
versionName "1.12.5"
versionCode 178
versionName "1.12.6"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
@@ -74,7 +74,11 @@
<release version="1.12.5" date="2025-12-13">
<release version="1.12.6" date="2025-12-22">
<url>https://github.com/Materialious/Materialious/releases/tag/1.12.6</url>
</release>
<release version="1.12.5" date="2025-12-13">
<url>https://github.com/Materialious/Materialious/releases/tag/1.12.5</url>
</release>
<release version="1.12.4" date="2025-12-11">
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "Materialious",
"version": "1.12.5",
"version": "1.12.6",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "Materialious",
"version": "1.12.5",
"version": "1.12.6",
"license": "MIT",
"dependencies": {
"@capacitor-community/electron": "^5.0.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "Materialious",
"version": "1.12.5",
"version": "1.12.6",
"description": "Modern material design for Invidious.",
"author": {
"name": "Ward Pearce",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "materialious",
"version": "1.12.5",
"version": "1.12.6",
"private": true,
"scripts": {
"dev": "vite dev",
@@ -75,4 +75,4 @@
"svelte-infinite-loading": "^1.4.0",
"youtubei.js": "^16.0.1"
}
}
}
+457 -113
View File
@@ -10,8 +10,6 @@
import ui from 'beercss';
import ISO6391 from 'iso-639-1';
import Mousetrap from 'mousetrap';
import 'shaka-player/dist/controls.css';
import '$lib/css/shaka-player-theme.css';
import { CapacitorMusicControls } from 'capacitor-music-controls-plugin';
import shaka from 'shaka-player/dist/shaka-player.ui';
import { SponsorBlock, type Category, type Segment } from 'sponsorblock-api';
@@ -22,9 +20,9 @@
import type { VideoPlay } from '../api/model';
import {
authStore,
darkModeStore,
instanceStore,
isAndroidTvStore,
playerAlwaysLoopStore,
playerAndroidLockOrientation,
playerAutoPlayStore,
playerCCByDefault,
@@ -34,31 +32,36 @@
playerProxyVideosStore,
playerSavePlaybackPositionStore,
playerState,
playerStatisticsByDefault,
playertheatreModeIsActive,
playerYouTubeJsFallback,
sponsorBlockCategoriesStore,
sponsorBlockDisplayToastStore,
sponsorBlockStore,
sponsorBlockUrlStore,
synciousInstanceStore,
synciousStore,
themeColorStore
synciousStore
} from '../store';
import { getDynamicTheme, setStatusBarColor } from '../theme';
import { setStatusBarColor } from '../theme';
import { patchYoutubeJs } from '$lib/patches/youtubejs';
import { goToNextVideo, goToPreviousVideo, playbackRates } from '$lib/player';
import { EndTimeElement } from '$lib/shaka-elements/endTime';
import { dashManifestDomainInclusion } from '$lib/android/youtube/dash';
import { injectSabr } from '$lib/sabr';
import type { SabrStreamingAdapter } from 'googlevideo/sabr-streaming-adapter';
import { SvelteSet } from 'svelte/reactivity';
interface Props {
data: { video: VideoPlay; content: PhasedDescription; playlistId: string | null };
isEmbed?: boolean;
playerElement?: HTMLMediaElement | undefined;
hideControls?: boolean;
}
let { data, isEmbed = false, playerElement = $bindable(undefined) }: Props = $props();
let {
data,
isEmbed = false,
playerElement = $bindable(undefined),
hideControls = false
}: Props = $props();
let segments: Segment[] = [];
@@ -69,23 +72,27 @@
let showVideoRetry = $state(false);
let player: shaka.Player;
let shakaUi: shaka.ui.Overlay;
let sabrAdapter: SabrStreamingAdapter | null;
let playerContainer: HTMLElement;
let playerCurrentPlaybackState = $state(false);
let playerCurrentTime = $state(0);
let playerMaxKnownTime = $state(0);
let playerIsSeeking = $state(false);
let playerIsBuffering = $state(false);
let playerVolume = $state(0);
let playerSettings: 'quality' | 'speed' | 'language' | 'root' = $state('root');
let playerTextTracks: shaka.extern.TextTrack[] | undefined = $state(undefined);
let playerCurrentVideoTrack: shaka.extern.VideoTrack | undefined = $state(undefined);
let playerCurrentAudioTrack: shaka.extern.AudioTrack | undefined = $state(undefined);
let playerLoop = $state($playerAlwaysLoopStore);
const STORAGE_KEY_VOLUME = 'shaka-preferred-volume';
async function updateSeekBarTheme() {
if (!shakaUi) return;
playertheatreModeIsActive.subscribe(async () => {
await tick();
shakaUi.configure({
seekBarColors: {
played: (await getDynamicTheme())['--primary']
}
});
}
themeColorStore.subscribe(updateSeekBarTheme);
darkModeStore.subscribe(updateSeekBarTheme);
updateVideoPlayerHeight();
});
function restoreDefaultLanguage() {
if (!$playerDefaultLanguage || $playerDefaultLanguage === 'original') {
@@ -109,8 +116,24 @@
}
}
function filterUniqueAudioTracks(tracks: shaka.extern.AudioTrack[]): shaka.extern.AudioTrack[] {
const uniqueTracks: shaka.extern.AudioTrack[] = [];
const seen = new SvelteSet<string>();
for (const track of tracks) {
const identifier = `${track.language}-${track.label || 'No Label'}`;
if (!seen.has(identifier)) {
seen.add(identifier);
uniqueTracks.push(track);
}
}
return uniqueTracks;
}
function saveVolumePreference() {
if (!playerElement) return;
playerVolume = playerElement.volume;
localStorage.setItem(STORAGE_KEY_VOLUME, playerElement.volume.toString());
}
@@ -118,6 +141,7 @@
const savedVolume = localStorage.getItem(STORAGE_KEY_VOLUME);
if (savedVolume && playerElement) {
playerElement.volume = parseFloat(savedVolume);
playerVolume = playerElement.volume;
}
}
@@ -298,20 +322,26 @@
page.subscribe((pageUpdate) => loadTimeFromUrl(pageUpdate));
function setActiveVideoTrack() {
const videoTracks = player.getVideoTracks();
playerCurrentVideoTrack = videoTracks.find((track) => track.active);
}
function setActiveAudioTrack() {
const audioTracks = player.getAudioTracks();
playerCurrentAudioTrack = audioTracks.find((track) => track.active);
}
async function loadVideo() {
showVideoRetry = false;
sabrAdapter = await injectSabr(data.video, player);
try {
document.getElementsByClassName('shaka-ad-info')[0].remove();
} catch {
// Continue regardless of error
}
player.addEventListener('loaded', () => {
restoreQualityPreference();
restoreDefaultLanguage();
setActiveVideoTrack();
setActiveAudioTrack();
if ($playerCCByDefault) {
toggleSubtitles();
@@ -421,14 +451,6 @@
if ($playerDefaultPlaybackSpeed && playerElement) {
playerElement.playbackRate = $playerDefaultPlaybackSpeed;
}
if ($playerStatisticsByDefault) {
// Appears to be no native way in shaka to toggle statistics on and off
const shakaStatisticsButton = document.querySelector('.shaka-statistics-button') as
| HTMLButtonElement
| undefined;
shakaStatisticsButton?.click();
}
}
async function reloadVideo() {
@@ -437,6 +459,24 @@
await loadVideo();
}
function toggleVideoPlaybackStatus() {
if (playerCurrentPlaybackState) {
playerElement?.pause();
} else {
playerElement?.play();
}
}
// 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() {
if (playerContainer) {
const height = playerContainer.getBoundingClientRect().height;
document.documentElement.style.setProperty('--video-player-height', `${height + 10}px`);
}
}
onMount(async () => {
shaka.polyfill.installAll();
if (!shaka.Player.isBrowserSupported()) {
@@ -466,66 +506,22 @@
});
playerElement = document.getElementById('player') as HTMLMediaElement;
playerElement.loop = playerLoop;
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();
const playerContainer = document.getElementById('shaka-container') as HTMLElement;
playerContainer = document.getElementById('player-container') as HTMLElement;
shakaUi = new shaka.ui.Overlay(player, playerContainer, playerElement);
window.addEventListener('resize', updateVideoPlayerHeight);
updateVideoPlayerHeight();
await player.attach(playerElement);
shaka.ui.Controls.registerElement('end_time', {
create: (parent: HTMLElement, controls: shaka.ui.Controls) => {
return new EndTimeElement(parent, controls);
}
});
shakaUi.configure({
addBigPlayButton: Capacitor.getPlatform() === 'android',
controlPanelElements: [
'play_pause',
Capacitor.getPlatform() === 'android' ? '' : 'volume',
'spacer',
'time_and_duration',
data.video.liveNow || data.video.lengthSeconds < 240 ? '' : 'end_time',
'captions',
'overflow_menu',
'fullscreen'
],
overflowMenuButtons: [
'cast',
'airplay',
'quality',
'playback_rate',
'loop',
'language',
'statistics'
],
playbackRates: playbackRates,
enableTooltips: false
});
updateSeekBarTheme();
player?.addEventListener('error', (event) => {
const error = (event as CustomEvent).detail as shaka.util.Error;
console.error('Player error:', error);
@@ -557,16 +553,6 @@
}
});
// Required to stop buttons from being still selected when fullscreening
document?.addEventListener('fullscreenchange', async () => {
const buttons = document.querySelectorAll('.shaka-controls-button-panel button');
buttons.forEach((button) => {
// Reset the button's focus and active states
(button as HTMLElement).blur(); // Remove focus from the button
button.removeAttribute('aria-pressed'); // Reset any ARIA attributes that might indicate selection
});
});
playerElement?.addEventListener('volumechange', saveVolumePreference);
await androidHandleRotate();
@@ -710,17 +696,6 @@
});
}
const volumeContainer = playerContainer.getElementsByClassName('shaka-volume-bar-container');
volumeContainer[0]?.addEventListener('mousewheel', (event) => {
event.preventDefault();
const delta = Math.sign((event as any).deltaY);
const newVolume = Math.max(
0,
Math.min(1, (playerElement as HTMLMediaElement).volume - delta * 0.05)
);
(playerElement as HTMLMediaElement).volume = newVolume;
});
Mousetrap.bind('c', () => {
toggleSubtitles();
return false;
@@ -730,7 +705,7 @@
if (document.fullscreenElement) {
document.exitFullscreen();
} else {
shakaUi.getControls()?.toggleFullScreen();
playerContainer.requestFullscreen();
}
return false;
});
@@ -765,14 +740,49 @@
playerElement.currentTime += frameTime;
});
const volumeContainer = document.getElementById('volume-slider');
volumeContainer?.addEventListener('mousewheel', (event) => {
event.preventDefault();
const delta = Math.sign((event as any).deltaY);
const newVolume = Math.max(
0,
Math.min(1, (playerElement as HTMLMediaElement).volume - delta * 0.05)
);
(playerElement as HTMLMediaElement).volume = newVolume;
playerVolume = newVolume;
});
playerElement?.addEventListener('pause', async () => {
playerCurrentPlaybackState = false;
playerIsBuffering = false;
savePlayerPos();
});
playerElement?.addEventListener('ended', async () => {
playerCurrentPlaybackState = false;
playerIsBuffering = false;
await goToNextVideo(data.video, data.playlistId);
});
playerElement?.addEventListener('playing', () => {
playerCurrentPlaybackState = true;
playerIsBuffering = false;
});
playerElement?.addEventListener('waiting', () => {
playerCurrentPlaybackState = false;
playerIsBuffering = true;
});
playerElement?.addEventListener('timeupdate', () => {
if (playerIsSeeking) return;
playerCurrentTime = playerElement?.currentTime ?? 0;
if (playerMaxKnownTime === 0 || playerCurrentTime > playerMaxKnownTime) {
playerMaxKnownTime = Number(playerElement?.currentTime);
}
});
try {
await loadVideo();
} catch (error: unknown) {
@@ -788,6 +798,11 @@
await reloadVideo();
}
}
// Update video player height again on video loaded.
updateVideoPlayerHeight();
playerTextTracks = player.getTextTracks();
});
async function getLastPlayPos(): Promise<number> {
@@ -869,6 +884,8 @@
// Continue regardless of error
}
window.removeEventListener('resize', updateVideoPlayerHeight);
Mousetrap.unbind(['left', 'right', 'space', 'c', 'f', 'shift+left', 'shift+right']);
if (watchProgressTimeout) {
@@ -883,20 +900,24 @@
player.unload();
player.destroy();
}
if (shakaUi) {
shakaUi.destroy();
}
});
</script>
<div
id="shaka-container"
class="player-theme"
id="player-container"
class:contain-video={!$isAndroidTvStore}
class:tv-contain-video={$isAndroidTvStore}
data-shaka-player-container
class:hide={showVideoRetry}
role="presentation"
onclick={(event) => {
if (
event.target &&
event.target instanceof HTMLElement &&
event.target.id === 'player-center'
) {
toggleVideoPlaybackStatus();
}
}}
>
<video
controls={false}
@@ -909,6 +930,276 @@
{data.video.title}
</div>
{/if}
<p id="mobile-time" class="chip primary s">
{#if data.video.liveNow}
{$_('thumbnail.live')}
{:else}
{videoLength(playerCurrentTime)} / {videoLength(data.video.lengthSeconds)}
{/if}
</p>
<div id="player-center">
{#if playerIsBuffering}
<progress class="circle large indeterminate" value="50" max="100"></progress>
{:else if !playerCurrentPlaybackState}
<button class="extra" onclick={toggleVideoPlaybackStatus}>
<i>play_arrow</i>
</button>
{/if}
</div>
{#if !hideControls}
<div id="player-controls">
<article class="round" style="width: 100%;padding: 0;height: 10px;">
<label class="slider max">
<input
oninput={() => {
playerIsSeeking = true;
if (playerElement) playerElement.currentTime = playerCurrentTime;
}}
onchange={() => (playerIsSeeking = false)}
type="range"
min="0"
bind:value={playerCurrentTime}
max={data.video.liveNow ? playerMaxKnownTime : data.video.lengthSeconds}
/>
<span></span>
</label>
</article>
<nav>
<nav class="no-wrap">
<button onclick={toggleVideoPlaybackStatus}>
<i>
{#if playerCurrentPlaybackState}
pause
{:else}
play_arrow
{/if}
</i>
</button>
{#if Capacitor.getPlatform() !== 'android'}
<article
id="volume-slider"
class="round m l"
style="padding: 0;height: 10px;width: 150px;"
>
<label class="slider max">
<input
oninput={() => {
if (!playerElement) return;
playerElement.volume = playerVolume;
}}
bind:value={playerVolume}
type="range"
step="0.1"
max="1"
/>
<span></span>
</label>
</article>
{/if}
</nav>
<div class="max"></div>
<nav class="no-wrap">
<p class="chip primary m l" style="border: none;">
{#if data.video.liveNow}
{$_('thumbnail.live')}
{:else}
{videoLength(playerCurrentTime)} / {videoLength(data.video.lengthSeconds)}
{/if}
</p>
{#if playerTextTracks && playerTextTracks.length > 0 && !data.video.liveNow}
<button>
<i>closed_caption</i>
<menu class="no-wrap mobile" id="cc-menu" data-ui="#cc-menu">
<li
role="presentation"
data-ui="#cc-menu"
onclick={() => player.setTextTrackVisibility(false)}
>
{$_('player.controls.off')}
</li>
{#each playerTextTracks as track (track)}
<li
role="presentation"
data-ui="#cc-menu"
onclick={() => {
player.selectTextTrack(track);
player.setTextTrackVisibility(true);
}}
>
{track.label}
</li>
{/each}
</menu>
</button>
{/if}
<button>
<i>settings</i>
<menu class="no-wrap mobile" id="settings-menu">
{#if playerSettings !== 'root'}
<li role="presentation" onclick={() => (playerSettings = 'root')}>
<i>arrow_back</i>
{$_('player.controls.back')}
</li>
{/if}
{#if playerSettings === 'root'}
<li role="presentation" onclick={() => (playerSettings = 'quality')}>
<nav class="no-wrap" style="width: 100%;">
<i>high_quality</i>
{$_('player.controls.quality')}
<div class="max"></div>
<span class="chip">
{#if playerCurrentVideoTrack}
{playerCurrentVideoTrack.height}p
{:else}
{$_('player.controls.auto')}
{/if}
</span>
</nav>
</li>
<li role="presentation" onclick={() => (playerSettings = 'speed')}>
<nav class="no-wrap" style="width: 100%;">
<i>speed</i>
{$_('player.controls.playbackSpeed')}
<div class="max"></div>
<span class="chip">
{playerElement?.playbackRate}x
</span>
</nav>
</li>
{#if playerCurrentAudioTrack && playerCurrentAudioTrack.label !== null}
<li role="presentation" onclick={() => (playerSettings = 'language')}>
<nav class="no-wrap" style="width: 100%;">
<i>language</i>
{$_('player.controls.language')}
<div class="max"></div>
<span class="chip">
{#if playerCurrentAudioTrack}
{playerCurrentAudioTrack.language !== 'und'
? ISO6391.getName(playerCurrentAudioTrack.language)
: playerCurrentAudioTrack.label}
{/if}
</span>
</nav>
</li>
{/if}
<li
role="presentation"
onclick={() => {
if (playerElement) playerElement.loop = !playerLoop;
playerLoop = !playerLoop;
}}
>
<nav class="no-wrap" style="width: 100%;">
<i>all_inclusive</i>
{$_('player.controls.loop')}
<div class="max"></div>
<span class="chip">
{playerLoop ? $_('player.controls.on') : $_('player.controls.off')}
</span>
</nav>
</li>
{:else if playerSettings === 'quality'}
<li
role="presentation"
onclick={() => {
playerSettings = 'root';
player.configure({ abr: true });
playerCurrentVideoTrack = undefined;
}}
>
{$_('player.controls.auto')}
</li>
{#each player.getVideoTracks().sort((a, b) => {
const heightA = a.height || 0;
const heightB = b.height || 0;
const widthA = a.width || 0;
const widthB = b.width || 0;
return heightB - heightA || widthB - widthA;
}) as track (track)}
<li
role="presentation"
onclick={() => {
playerSettings = 'root';
player.selectVideoTrack(track, true);
setActiveVideoTrack();
}}
>
{track.height}p
</li>
{/each}
{:else if playerSettings === 'speed'}
{#each playbackRates as playbackRate (playbackRate)}
<li
role="presentation"
onclick={() => {
playerSettings = 'root';
if (playerElement) playerElement.playbackRate = playbackRate;
}}
>
{playbackRate}
</li>
{/each}
{:else if playerSettings === 'language'}
{#each filterUniqueAudioTracks(player.getAudioTracks()) as track (track)}
<li
role="presentation"
onclick={() => {
playerSettings = 'root';
player.selectAudioTrack(track);
setActiveAudioTrack();
}}
>
{#if track.language !== 'und'}
{ISO6391.getName(track.language)} -
{/if}
{track.label}
</li>
{/each}
{/if}
</menu>
</button>
{#if document.pictureInPictureEnabled}
<button
onclick={() => {
(playerElement as HTMLVideoElement).requestPictureInPicture();
}}
>
<i>pip</i>
</button>
{/if}
<button
onclick={() => {
if (document.fullscreenElement) {
document.exitFullscreen();
} else {
playerContainer.requestFullscreen();
}
}}
>
<i>
{#if document.fullscreenElement}
fullscreen_exit
{:else}
fullscreen
{/if}
</i>
</button>
</nav>
</nav>
</div>
{/if}
</div>
{#if showVideoRetry}
@@ -938,6 +1229,59 @@
{/if}
<style>
#player-container {
position: relative;
width: 100%;
}
#player-controls {
position: absolute;
bottom: 0;
left: 0;
right: 0;
opacity: 0;
transition: opacity 2s ease;
padding: 10px;
}
#player-center {
position: absolute;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
#mobile-time {
position: absolute;
top: 10px;
right: 10px;
border: none;
opacity: 0;
transition: opacity 2s ease;
}
#player-container:focus-within #player-controls,
#player-container:active #player-controls,
#player-container:hover #player-controls {
opacity: 1;
transition: opacity 0.3s ease;
}
#player-container:focus-within #mobile-time,
#player-container:active #mobile-time,
#player-container:hover #mobile-time {
opacity: 1;
transition: opacity 0.3s ease;
}
menu.mobile {
transform: scale(1) translateY(-40%) translateX(0);
width: 300px;
height: 200px;
}
.contain-video {
max-height: 80vh;
max-width: calc(80vh * 16 / 9);
@@ -16,6 +16,7 @@
interfaceLowBandwidthMode,
isAndroidTvStore,
playerSavePlaybackPositionStore,
playerState,
syncPartyConnectionsStore,
syncPartyPeerStore,
synciousInstanceStore,
@@ -119,7 +120,9 @@
removeEventListener('resize', disableSideways);
});
function syncChangeVideo() {
function onVideoSelected() {
playerState.set(undefined);
if ($syncPartyConnectionsStore) {
const events = {
events: [{ type: 'change-video', videoId: video.videoId }]
@@ -166,7 +169,7 @@
class="wave thumbnail"
href={watchUrl.toString()}
data-sveltekit-preload-data="off"
onclick={syncChangeVideo}
onclick={onVideoSelected}
>
{#if !$interfaceLowBandwidthMode}
{#if !thumbnail}
@@ -213,6 +216,7 @@
class="video-title"
data-sveltekit-preload-data="off"
href={watchUrl.toString()}
onclick={onVideoSelected}
>
<span class="bold">{letterCase(video.title.trimEnd())}</span>
</a>
@@ -253,6 +257,7 @@
.thumbnail {
width: 100%;
overflow: hidden;
display: inline-block;
}
.thumbnail img {
+3 -1
View File
@@ -79,9 +79,11 @@ menu {
@media screen and (max-width: 1000px) {
menu.mobile {
position: fixed !important;
top: 10% !important;
bottom: 0% !important;
height: 100vh !important;
width: 100% !important;
z-index: 99999;
transform: unset !important;
}
main.root {
@@ -1,120 +0,0 @@
.shaka-text-container > div {
align-items: center !important;
}
.player-theme {
font-family: var(--font) !important;
color: var(--on-surface) !important;
}
.shaka-current-time {
color: #fff !important;
opacity: 1 !important;
}
.player-theme .shaka-video-container * {
font-family: var(--font) !important;
color: var(--on-surface) !important;
}
.player-theme .shaka-overflow-menu,
.player-theme .shaka-settings-menu {
border-radius: 0.25rem;
background: var(--surface-container-low);
animation: none !important;
transition: none !important;
opacity: 1 !important;
visibility: visible !important;
z-index: 99999999 !important;
pointer-events: auto !important;
max-height: 400px;
}
.shaka-overflow-menu button,
.shaka-settings-menu button {
color: var(--text) !important;
border: none !important;
outline: none !important;
}
@media screen and (max-width: 650px) {
.player-theme .shaka-end-time {
display: none;
}
.player-theme .chapter-marker {
height: 8px;
}
.player-theme .shaka-overflow-menu,
.player-theme .shaka-settings-menu {
width: 100% !important;
min-width: 100% !important;
position: fixed !important;
top: 70% !important;
left: 50% !important;
height: 50vh !important;
transform: translate(-50%, -50%) !important;
pointer-events: auto !important;
opacity: 1 !important;
visibility: visible !important;
padding: 1rem 0.5rem !important;
box-shadow: var(--elevate2);
background-color: var(--surface-container);
z-index: 40;
inset: auto auto 0 0;
inline-size: 100%;
max-block-size: 50vh;
max-inline-size: none !important;
overflow-x: hidden;
overflow-y: auto;
font-size: 0.875rem;
font-weight: 400;
text-transform: none;
color: var(--on-surface);
line-height: normal;
text-align: start;
border-radius: 0.25rem;
transition:
all var(--speed2),
0s background-color;
}
.player-theme .shaka-overflow-menu::before,
.player-theme .shaka-settings-menu::before {
content: '' !important;
position: fixed !important;
top: 0 !important;
left: 0 !important;
width: 100vw !important;
height: 100vh !important;
background: transparent !important;
pointer-events: auto !important;
}
.player-theme .shaka-overflow-menu:hover,
.player-theme .shaka-settings-menu:hover {
pointer-events: auto !important;
opacity: 1 !important;
visibility: visible !important;
}
#shaka-container {
z-index: 98 !important;
pointer-events: auto !important;
opacity: 1 !important;
visibility: visible !important;
}
}
@media only screen and (width <=400px) {
:deep(.shaka-text-container) {
font-size: 16px !important;
}
.player-theme .shaka-overflow-menu,
.player-theme .shaka-settings-menu {
max-height: 80%;
overflow-y: scroll;
}
}
+3 -1
View File
@@ -205,5 +205,7 @@
},
"newest": "Neuste",
"oldest": "Älteste",
"popular": "Populär"
"popular": "Populär",
"favouriteChannel": "Kanal favorisieren",
"unfavouriteChannel": "Kanal entfavorisieren"
}
+11 -1
View File
@@ -124,7 +124,17 @@
"youtubeJsLoading": "Video failed to load, attempting to reload with local video fallback.",
"retryText": "Video failed to load, how would you like to continue?",
"enableYoutubejsTemp": "Use local video fallback once",
"enableYoutubejsPerm": "Always allow local video fallback"
"enableYoutubejsPerm": "Always allow local video fallback",
"controls": {
"back": "Back",
"quality": "Quality",
"auto": "Auto",
"playbackSpeed": "Playback speed",
"loop": "Loop",
"on": "On",
"off": "Off",
"language": "Language"
}
},
"layout": {
"interface": "Interface",
+3 -1
View File
@@ -205,5 +205,7 @@
},
"newest": "Neusti",
"oldest": "Ältisti",
"popular": "Populär"
"popular": "Populär",
"favouriteChannel": "Kanal favorisiere",
"unfavouriteChannel": "Kanal entfavorisiere"
}
+3 -1
View File
@@ -205,5 +205,7 @@
},
"newest": "Legújabb",
"oldest": "Legrégebbi",
"popular": "Népszerű"
"popular": "Népszerű",
"favouriteChannel": "Csatorna kedvelése",
"unfavouriteChannel": "Csatorna eltávolítása a kedvencekből"
}
+7 -3
View File
@@ -75,7 +75,8 @@
"materialiousLink": "Скопировать ссылку на Materialious",
"invidiousRedirect": "Скопировать ссылку на сервер Invidious",
"youtubeLink": "Скопировать ссылку на Youtube",
"includeTimestamp": "С отметкой времени"
"includeTimestamp": "С отметкой времени",
"copiedSuccess": "Скопированно в буфер обмена"
},
"download": "Скачать",
"downloadSteps": {
@@ -176,7 +177,8 @@
"companionUrl": "URL-адрес компаньона",
"expandChapters": "Раскрывать главы по умолчанию",
"disableAutoUpdate": "Отключить автоматические обновления",
"about": "О приложении"
"about": "О приложении",
"androidNativeShare": "Использовать native share"
},
"subscribe": "Подписаться",
"invalidInstance": "Проверьте URL-адрес. Если он правильный, то возможно сервер сейчас недоступен или не поддерживает сторонние клиенты.",
@@ -203,5 +205,7 @@
"contributors": "Участники",
"newest": "Сначала новые",
"oldest": "Сначала старые",
"popular": "Популярные"
"popular": "Популярные",
"favouriteChannel": "Любимый канал",
"unfavouriteChannel": "Нелюбимый канал"
}
@@ -1,41 +0,0 @@
import shaka from 'shaka-player/dist/shaka-player.ui';
export class EndTimeElement extends shaka.ui.Element {
public video: HTMLMediaElement;
protected button: HTMLButtonElement;
constructor(parent: HTMLElement, controls: shaka.ui.Controls) {
super(parent, controls);
this.video = (this.player as shaka.Player).getMediaElement() as HTMLMediaElement;
this.button = document.createElement('button');
this.button.classList.add('shaka-current-time', 'shaka-end-time');
this.button.disabled = true;
(this.parent as HTMLElement).appendChild(this.button);
this.updateTime = this.updateTime.bind(this);
(this.eventManager as shaka.util.EventManager).listen(
this.video,
'timeupdate',
this.updateTime
);
}
private updateTime(): void {
const duration = this.video.duration;
const currentTime = this.video.currentTime;
if (isNaN(duration) || isNaN(currentTime)) return;
const remainingTime: number = duration - currentTime;
const end: Date = new Date(Date.now() + remainingTime * 1000);
const formatted: string = end.toLocaleTimeString([], {
hour: '2-digit',
minute: '2-digit',
hour12: true
});
this.button.textContent = `Ends at ${formatted}`;
}
}
+2 -1
View File
@@ -418,6 +418,7 @@
<i>close</i>
</button>
</nav>
<div class="space"></div>
<nav class="s">
<a
class="button border"
@@ -432,7 +433,7 @@
{/if}
<div class="player">
{#key $playerState.data.video.videoId}
<Player data={$playerState.data} />
<Player data={$playerState.data} hideControls={playerIsPip} />
{/key}
</div>
</div>
@@ -390,6 +390,7 @@
{premiereTime}
</h6>
</article>
<div class="space"></div>
{/if}
</div>
@@ -718,6 +719,10 @@
justify-content: flex-end;
}
.video-placeholder {
height: 50vh;
}
@media screen and (max-width: 1000px) {
.video-actions {
align-items: flex-start;
@@ -112,7 +112,7 @@
</script>
{#key data.video.videoId}
<Player bind:playerElement isEmbed={true} {data} />
<Player bind:playerElement isEmbed={true} {data} hideControls={true} />
{/key}
{#if showInfo}
+1 -1
View File
@@ -3,7 +3,7 @@ import os
import re
from datetime import datetime
LATEST_VERSION = "1.12.5"
LATEST_VERSION = "1.12.6"
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")