diff --git a/package-lock.json b/package-lock.json
index d0c7ad763..642d0ea73 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "stremio",
- "version": "5.0.0-beta.17",
+ "version": "5.0.0-beta.18",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "stremio",
- "version": "5.0.0-beta.17",
+ "version": "5.0.0-beta.18",
"license": "gpl-2.0",
"dependencies": {
"@babel/runtime": "7.26.0",
@@ -14,7 +14,7 @@
"@stremio/stremio-colors": "5.2.0",
"@stremio/stremio-core-web": "0.48.5",
"@stremio/stremio-icons": "5.4.1",
- "@stremio/stremio-video": "0.0.52",
+ "@stremio/stremio-video": "0.0.53",
"a-color-picker": "1.2.1",
"bowser": "2.11.0",
"buffer": "6.0.3",
@@ -36,7 +36,7 @@
"react-i18next": "^15.1.3",
"react-is": "18.3.1",
"spatial-navigation-polyfill": "github:Stremio/spatial-navigation#64871b1422466f5f45d24ebc8bbd315b2ebab6a6",
- "stremio-translations": "github:Stremio/stremio-translations#a0f50634202f748a57907b645d2cd92fbaa479dd",
+ "stremio-translations": "github:Stremio/stremio-translations#62bcc6e8f44258203c7375af59210771efb6f634",
"url": "0.11.4",
"use-long-press": "^3.2.0"
},
@@ -3409,10 +3409,9 @@
]
},
"node_modules/@stremio/stremio-video": {
- "version": "0.0.52",
- "resolved": "https://registry.npmjs.org/@stremio/stremio-video/-/stremio-video-0.0.52.tgz",
- "integrity": "sha512-OlHC8FIvYEyGXcNAM4W044Dqx6CmGb5BV3fDU361SyUjO9gKXXUWdL7LwmwHeWFeuy2sK1MEg4AT2JPptvJ0rg==",
- "license": "MIT",
+ "version": "0.0.53",
+ "resolved": "https://registry.npmjs.org/@stremio/stremio-video/-/stremio-video-0.0.53.tgz",
+ "integrity": "sha512-hSlk8GqMdk4N8VbcdvduYqWVZsQLgHyU7GfFmd1k+t0pSpDKAhI3C6dohG5Sr09CKCjHa8D1rls+CwMNPXLSGw==",
"dependencies": {
"buffer": "6.0.3",
"color": "4.2.3",
@@ -13374,8 +13373,8 @@
},
"node_modules/stremio-translations": {
"version": "1.44.9",
- "resolved": "git+ssh://git@github.com/Stremio/stremio-translations.git#a0f50634202f748a57907b645d2cd92fbaa479dd",
- "integrity": "sha512-JJpd1JJet3T6/VTNdZ2NZ7uvHJ4zkuyqo5BnTcDGqLVNO/OpicGqKhZjE4WGSgmuhsfPBU8T0ICCfzKu2xpvKg==",
+ "resolved": "git+ssh://git@github.com/Stremio/stremio-translations.git#62bcc6e8f44258203c7375af59210771efb6f634",
+ "integrity": "sha512-8Sc5Qvd4IiObwGXkmj1XFXFavUc15My5po6G48HHDBbp42SVc5I/t7h+1yxW1A81byyBCXbL23a9iU9v49vpQA==",
"license": "MIT"
},
"node_modules/string_decoder": {
diff --git a/package.json b/package.json
index e80305bc9..9ccb0cee8 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "stremio",
"displayName": "Stremio",
- "version": "5.0.0-beta.17",
+ "version": "5.0.0-beta.18",
"author": "Smart Code OOD",
"private": true,
"license": "gpl-2.0",
@@ -18,7 +18,7 @@
"@stremio/stremio-colors": "5.2.0",
"@stremio/stremio-core-web": "0.48.5",
"@stremio/stremio-icons": "5.4.1",
- "@stremio/stremio-video": "0.0.52",
+ "@stremio/stremio-video": "0.0.53",
"a-color-picker": "1.2.1",
"bowser": "2.11.0",
"buffer": "6.0.3",
@@ -40,7 +40,7 @@
"react-i18next": "^15.1.3",
"react-is": "18.3.1",
"spatial-navigation-polyfill": "github:Stremio/spatial-navigation#64871b1422466f5f45d24ebc8bbd315b2ebab6a6",
- "stremio-translations": "github:Stremio/stremio-translations#a0f50634202f748a57907b645d2cd92fbaa479dd",
+ "stremio-translations": "github:Stremio/stremio-translations#62bcc6e8f44258203c7375af59210771efb6f634",
"url": "0.11.4",
"use-long-press": "^3.2.0"
},
diff --git a/src/App/App.js b/src/App/App.js
index 730e75f28..6dc2d6e0b 100644
--- a/src/App/App.js
+++ b/src/App/App.js
@@ -6,10 +6,11 @@ const { useTranslation } = require('react-i18next');
const { Router } = require('stremio-router');
const { Core, Shell, Chromecast, DragAndDrop, KeyboardShortcuts, ServicesProvider } = require('stremio/services');
const { NotFound } = require('stremio/routes');
-const { PlatformProvider, ToastProvider, TooltipProvider, CONSTANTS, withCoreSuspender } = require('stremio/common');
+const { FileDropProvider, PlatformProvider, ToastProvider, TooltipProvider, CONSTANTS, withCoreSuspender } = require('stremio/common');
const ServicesToaster = require('./ServicesToaster');
const DeepLinkHandler = require('./DeepLinkHandler');
const SearchParamsHandler = require('./SearchParamsHandler');
+const { default: UpdaterBanner } = require('./UpdaterBanner');
const ErrorDialog = require('./ErrorDialog');
const withProtectedRoutes = require('./withProtectedRoutes');
const routerViewsConfig = require('./routerViewsConfig');
@@ -165,14 +166,17 @@ const App = () => {
-
-
-
-
+
+
+
+
+
+
+
diff --git a/src/App/UpdaterBanner/UpdaterBanner.less b/src/App/UpdaterBanner/UpdaterBanner.less
new file mode 100644
index 000000000..9928fb493
--- /dev/null
+++ b/src/App/UpdaterBanner/UpdaterBanner.less
@@ -0,0 +1,46 @@
+.updater-banner {
+ height: 4rem;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 1rem;
+ padding: 0 1rem;
+ font-size: 1rem;
+ font-weight: bold;
+ color: var(--primary-foreground-color);
+ background-color: var(--primary-accent-color);
+
+ .button {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: center;
+ height: 2.5rem;
+ padding: 0 1rem;
+ border-radius: var(--border-radius);
+ color: var(--primary-background-color);
+ background-color: var(--primary-foreground-color);
+ transition: all 0.1s ease-out;
+
+ &:hover {
+ color: var(--primary-foreground-color);
+ background-color: transparent;
+ box-shadow: inset 0 0 0 0.15rem var(--primary-foreground-color);
+ }
+ }
+
+ .close {
+ position: absolute;
+ right: 0;
+ height: 4rem;
+ width: 4rem;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: center;
+
+ .icon {
+ height: 2rem;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/App/UpdaterBanner/UpdaterBanner.tsx b/src/App/UpdaterBanner/UpdaterBanner.tsx
new file mode 100644
index 000000000..3d421deb5
--- /dev/null
+++ b/src/App/UpdaterBanner/UpdaterBanner.tsx
@@ -0,0 +1,50 @@
+import React, { useEffect } from 'react';
+import Icon from '@stremio/stremio-icons/react';
+import { useTranslation } from 'react-i18next';
+import { useServices } from 'stremio/services';
+import { useBinaryState, useShell } from 'stremio/common';
+import { Button, Transition } from 'stremio/components';
+import styles from './UpdaterBanner.less';
+
+type Props = {
+ className: string,
+};
+
+const UpdaterBanner = ({ className }: Props) => {
+ const { t } = useTranslation();
+ const { shell } = useServices();
+ const shellTransport = useShell();
+ const [visible, show, hide] = useBinaryState(false);
+
+ const onInstallClick = () => {
+ shellTransport.send('autoupdater-notif-clicked');
+ };
+
+ useEffect(() => {
+ shell.transport && shell.transport.on('autoupdater-show-notif', show);
+
+ return () => {
+ shell.transport && shell.transport.off('autoupdater-show-notif', show);
+ };
+ }, []);
+
+ return (
+
+
+
+
+ { t('UPDATER_TITLE') }
+
+
+
+
+
+
+ );
+};
+
+export default UpdaterBanner;
diff --git a/src/App/UpdaterBanner/index.ts b/src/App/UpdaterBanner/index.ts
new file mode 100644
index 000000000..e4306ecb2
--- /dev/null
+++ b/src/App/UpdaterBanner/index.ts
@@ -0,0 +1,2 @@
+import UpdaterBanner from './UpdaterBanner';
+export default UpdaterBanner;
diff --git a/src/App/styles.less b/src/App/styles.less
index 7e44a9643..50819f883 100644
--- a/src/App/styles.less
+++ b/src/App/styles.less
@@ -204,12 +204,32 @@ html {
background-color: var(--modal-background-color);
box-shadow: var(--outer-glow);
transition: opacity 0.1s ease-out;
+ }
+
+ .file-drop-container {
+ position: fixed;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ border-radius: 1rem;
+ border: 0.5rem dashed transparent;
+ pointer-events: none;
+ transition: border-color 0.25s ease-out;
&:global(.active) {
- transition-delay: 0.25s;
+ border-color: var(--primary-accent-color);
}
}
+ .updater-banner-container {
+ z-index: 1;
+ position: absolute;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ }
+
.router {
width: 100%;
height: 100%;
diff --git a/src/common/CONSTANTS.js b/src/common/CONSTANTS.js
index 727c152fa..8e4e3efdc 100644
--- a/src/common/CONSTANTS.js
+++ b/src/common/CONSTANTS.js
@@ -41,6 +41,16 @@ const ICON_FOR_TYPE = new Map([
['other', 'movies'],
]);
+const MIME_SIGNATURES = {
+ 'application/x-subrip': ['310D0A', '310A'],
+ 'text/vtt': ['574542565454'],
+};
+
+const SUPPORTED_LOCAL_SUBTITLES = [
+ 'application/x-subrip',
+ 'text/vtt',
+];
+
const EXTERNAL_PLAYERS = [
{
label: 'EXTERNAL_PLAYER_DISABLED',
@@ -113,6 +123,8 @@ module.exports = {
WRITERS_LINK_CATEGORY,
TYPE_PRIORITIES,
ICON_FOR_TYPE,
+ MIME_SIGNATURES,
+ SUPPORTED_LOCAL_SUBTITLES,
EXTERNAL_PLAYERS,
WHITELISTED_HOSTS,
};
diff --git a/src/common/FileDrop/FileDrop.tsx b/src/common/FileDrop/FileDrop.tsx
new file mode 100644
index 000000000..aae4e146b
--- /dev/null
+++ b/src/common/FileDrop/FileDrop.tsx
@@ -0,0 +1,91 @@
+import React, { createContext, useCallback, useContext, useEffect, useState } from 'react';
+import classNames from 'classnames';
+import { isFileType } from './utils';
+
+export type FileType = string;
+export type FileDropListener = (filename: string, buffer: ArrayBuffer) => void;
+
+type FileDropContext = {
+ on: (type: FileType, listener: FileDropListener) => void,
+ off: (type: FileType, listener: FileDropListener) => void,
+};
+
+const FileDropContext = createContext({} as FileDropContext);
+
+type Props = {
+ className: string,
+ children: JSX.Element,
+};
+
+const FileDropProvider = ({ className, children }: Props) => {
+ const [listeners, setListeners] = useState<[FileType, FileDropListener][]>([]);
+ const [active, setActive] = useState(false);
+
+ const onDragOver = (event: DragEvent) => {
+ event.preventDefault();
+ setActive(true);
+ };
+
+ const onDragLeave = () => {
+ setActive(false);
+ };
+
+ const onDrop = useCallback((event: DragEvent) => {
+ event.preventDefault();
+ const { dataTransfer } = event;
+
+ if (dataTransfer && dataTransfer?.files.length > 0) {
+ const file = dataTransfer.files[0];
+
+ file
+ .arrayBuffer()
+ .then((buffer) => {
+ listeners
+ .filter(([type]) => file.type ? type === file.type : isFileType(buffer, type))
+ .forEach(([, listerner]) => listerner(file.name, buffer));
+ });
+ }
+
+ setActive(false);
+ }, [listeners]);
+
+ const on = (type: FileType, listener: FileDropListener) => {
+ setListeners((listeners) => {
+ return [...listeners, [type, listener]];
+ });
+ };
+
+ const off = (type: FileType, listener: FileDropListener) => {
+ setListeners((listeners) => {
+ return listeners.filter(([key, value]) => key !== type && value !== listener);
+ });
+ };
+
+ useEffect(() => {
+ window.addEventListener('dragover', onDragOver);
+ window.addEventListener('dragleave', onDragLeave);
+ window.addEventListener('drop', onDrop);
+
+ return () => {
+ window.removeEventListener('dragover', onDragOver);
+ window.removeEventListener('dragleave', onDragLeave);
+ window.removeEventListener('drop', onDrop);
+ };
+ }, [onDrop]);
+
+ return (
+
+ { children }
+
+
+ );
+};
+
+const useFileDrop = () => {
+ return useContext(FileDropContext);
+};
+
+export {
+ FileDropProvider,
+ useFileDrop,
+};
diff --git a/src/common/FileDrop/index.ts b/src/common/FileDrop/index.ts
new file mode 100644
index 000000000..41acfbfd7
--- /dev/null
+++ b/src/common/FileDrop/index.ts
@@ -0,0 +1,8 @@
+import { FileDropProvider, useFileDrop } from './FileDrop';
+import onFileDrop from './onFileDrop';
+
+export {
+ FileDropProvider,
+ useFileDrop,
+ onFileDrop,
+};
diff --git a/src/common/FileDrop/onFileDrop.ts b/src/common/FileDrop/onFileDrop.ts
new file mode 100644
index 000000000..339b0219b
--- /dev/null
+++ b/src/common/FileDrop/onFileDrop.ts
@@ -0,0 +1,14 @@
+import { useEffect } from 'react';
+import { type FileType, type FileDropListener, useFileDrop } from './FileDrop';
+
+const onFileDrop = (types: FileType[], listener: FileDropListener) => {
+ const { on, off } = useFileDrop();
+
+ useEffect(() => {
+ types.forEach((type) => on(type, listener));
+
+ return () => types.forEach((type) => off(type, listener));
+ }, []);
+};
+
+export default onFileDrop;
diff --git a/src/common/FileDrop/utils.ts b/src/common/FileDrop/utils.ts
new file mode 100644
index 000000000..f9996aed3
--- /dev/null
+++ b/src/common/FileDrop/utils.ts
@@ -0,0 +1,19 @@
+import { MIME_SIGNATURES } from 'stremio/common/CONSTANTS';
+
+const SIGNATURES = MIME_SIGNATURES as Record;
+
+const isFileType = (buffer: ArrayBuffer, type: string) => {
+ const signatures = SIGNATURES[type];
+
+ return signatures.some((signature) => {
+ const array = new Uint8Array(buffer);
+ const signatureBuffer = Buffer.from(signature, 'hex');
+ const bufferToCompare = array.subarray(0, signatureBuffer.length);
+
+ return Buffer.compare(signatureBuffer, bufferToCompare) === 0;
+ });
+};
+
+export {
+ isFileType,
+};
diff --git a/src/common/Platform/Platform.tsx b/src/common/Platform/Platform.tsx
index 41e74b24f..2212303e4 100644
--- a/src/common/Platform/Platform.tsx
+++ b/src/common/Platform/Platform.tsx
@@ -1,6 +1,6 @@
import React, { createContext, useContext } from 'react';
import { WHITELISTED_HOSTS } from 'stremio/common/CONSTANTS';
-import useShell from './useShell';
+import useShell from 'stremio/common/useShell';
import { name, isMobile } from './device';
interface PlatformContext {
diff --git a/src/common/animations.less b/src/common/animations.less
index c7a30d2fb..91dbe386d 100644
--- a/src/common/animations.less
+++ b/src/common/animations.less
@@ -69,6 +69,19 @@
transform: translateX(100%);
}
+.slide-up-enter {
+ transform: translateY(100%);
+}
+
+.slide-up-active {
+ transform: translateY(0%);
+ transition: transform 0.3s cubic-bezier(0.32, 0, 0.67, 0);
+}
+
+.slide-up-exit {
+ transform: translateY(100%);
+}
+
@keyframes fade-in-no-motion {
0% {
opacity: 0;
diff --git a/src/common/index.js b/src/common/index.js
index 4c514dfbe..4acf8b056 100644
--- a/src/common/index.js
+++ b/src/common/index.js
@@ -1,5 +1,6 @@
// Copyright (C) 2017-2023 Smart code 203358507
+const { FileDropProvider, onFileDrop } = require('./FileDrop');
const { PlatformProvider, usePlatform } = require('./Platform');
const { ToastProvider, useToast } = require('./Toast');
const { TooltipProvider, Tooltip } = require('./Tooltips');
@@ -19,11 +20,14 @@ const useModelState = require('./useModelState');
const useNotifications = require('./useNotifications');
const useOnScrollToBottom = require('./useOnScrollToBottom');
const useProfile = require('./useProfile');
+const { default: useShell } = require('./useShell');
const useStreamingServer = require('./useStreamingServer');
const useTorrent = require('./useTorrent');
const useTranslate = require('./useTranslate');
module.exports = {
+ FileDropProvider,
+ onFileDrop,
PlatformProvider,
usePlatform,
ToastProvider,
@@ -47,6 +51,7 @@ module.exports = {
useNotifications,
useOnScrollToBottom,
useProfile,
+ useShell,
useStreamingServer,
useTorrent,
useTranslate,
diff --git a/src/common/Platform/useShell.ts b/src/common/useShell.ts
similarity index 100%
rename from src/common/Platform/useShell.ts
rename to src/common/useShell.ts
diff --git a/src/components/Button/Button.tsx b/src/components/Button/Button.tsx
index f4b059bd3..f97e1ba82 100644
--- a/src/components/Button/Button.tsx
+++ b/src/components/Button/Button.tsx
@@ -8,6 +8,7 @@ import styles from './Button.less';
type Props = {
className?: string,
href?: string,
+ target?: string
title?: string,
disabled?: boolean,
tabIndex?: number,
diff --git a/src/components/Checkbox/Checkbox.less b/src/components/Checkbox/Checkbox.less
new file mode 100644
index 000000000..718a7b129
--- /dev/null
+++ b/src/components/Checkbox/Checkbox.less
@@ -0,0 +1,83 @@
+// Copyright (C) 2017-2025 Smart code 203358507
+
+@import (reference) '~@stremio/stremio-colors/less/stremio-colors.less';
+
+.checkbox {
+ display: flex;
+ align-items: center;
+ overflow: visible;
+
+ .label {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ padding: 0.5rem 0;
+ cursor: pointer;
+
+ span {
+ font-size: 0.9rem;
+ color: var(--primary-foreground-color);
+ opacity: 0.6;
+ }
+
+ .link {
+ font-size: 0.9rem;
+ color: var(--primary-accent-color);
+
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+ }
+
+ .checkbox-container {
+ position: relative;
+ width: 1.5rem;
+ height: 1.5rem;
+ border-radius: 0.3rem;
+ background-color: var(--overlay-color);
+ padding: 0.1rem;
+ display: flex;
+ flex: none;
+ margin: 0 1rem 0 0.3rem;
+ align-items: center;
+ justify-content: center;
+ transition: all 0.2s ease-in-out;
+ cursor: pointer;
+ outline: none;
+ user-select: none;
+ outline-width: var(--focus-outline-size);
+ outline-color: @color-surface-light5;
+ outline-offset: 2px;
+
+ input[type='checkbox'] {
+ opacity: 0;
+ width: 0;
+ height: 0;
+ position: absolute;
+ cursor: pointer;
+ }
+
+ .checkbox-icon {
+ width: 100%;
+ height: 100%;
+ color: var(--primary-foreground-color);
+ }
+
+ &.disabled {
+ cursor: not-allowed;
+ }
+
+ &.error {
+ border-color: var(--color-trakt);
+ }
+
+ &.checked {
+ background-color: var(--primary-accent-color);
+ }
+
+ &:hover, &:focus {
+ outline-style: solid;
+ }
+ }
+}
diff --git a/src/components/Checkbox/Checkbox.tsx b/src/components/Checkbox/Checkbox.tsx
new file mode 100644
index 000000000..da4ae33eb
--- /dev/null
+++ b/src/components/Checkbox/Checkbox.tsx
@@ -0,0 +1,97 @@
+// Copyright (C) 2017-2025 Smart code 203358507
+
+import React, { useCallback, ChangeEvent, KeyboardEvent, RefCallback } from 'react';
+import classNames from 'classnames';
+import styles from './Checkbox.less';
+import Button from '../Button';
+import Icon from '@stremio/stremio-icons/react';
+
+type Props = {
+ ref?: RefCallback;
+ name: string;
+ disabled?: boolean;
+ checked?: boolean;
+ className?: string;
+ label?: string;
+ link?: string;
+ href?: string;
+ onChange?: (props: {
+ type: string;
+ checked: boolean;
+ reactEvent: KeyboardEvent | ChangeEvent;
+ nativeEvent: Event;
+ }) => void;
+ error?: string;
+};
+
+const Checkbox = React.forwardRef(({ name, disabled, className, label, href, link, onChange, error, checked }, ref) => {
+
+ const handleSelect = useCallback((event: ChangeEvent) => {
+ if (!disabled && onChange) {
+ onChange({
+ type: 'select',
+ checked: event.target.checked,
+ reactEvent: event,
+ nativeEvent: event.nativeEvent,
+ });
+ }
+ }, [disabled, onChange]);
+
+ const onKeyDown = useCallback((event: KeyboardEvent) => {
+ if ((event.key === 'Enter' || event.key === ' ') && !disabled) {
+ onChange && onChange({
+ type: 'select',
+ checked: !checked,
+ reactEvent: event as KeyboardEvent,
+ nativeEvent: event.nativeEvent,
+ });
+ }
+ }, [disabled, checked, onChange]);
+
+ return (
+
+
+
+ );
+});
+
+export default Checkbox;
diff --git a/src/components/Checkbox/index.ts b/src/components/Checkbox/index.ts
new file mode 100644
index 000000000..fa5739580
--- /dev/null
+++ b/src/components/Checkbox/index.ts
@@ -0,0 +1,5 @@
+// Copyright (C) 2017-2025 Smart code 203358507
+
+import Checkbox from './Checkbox';
+
+export default Checkbox;
diff --git a/src/components/EventModal/EventModal.js b/src/components/EventModal/EventModal.js
index 917190ba1..f4e46b199 100644
--- a/src/components/EventModal/EventModal.js
+++ b/src/components/EventModal/EventModal.js
@@ -2,7 +2,8 @@
const React = require('react');
const { useTranslation } = require('react-i18next');
-const { Button, ModalDialog } = require('stremio/components');
+const { default: Button } = require('stremio/components/Button');
+const ModalDialog = require('stremio/components/ModalDialog');
const useEvents = require('./useEvents');
const styles = require('./styles');
const { default: Icon } = require('@stremio/stremio-icons/react');
diff --git a/src/components/NavBar/VerticalNavBar/styles.less b/src/components/NavBar/VerticalNavBar/styles.less
index 3a6ae76d8..7d17d6054 100644
--- a/src/components/NavBar/VerticalNavBar/styles.less
+++ b/src/components/NavBar/VerticalNavBar/styles.less
@@ -13,6 +13,7 @@
background-color: transparent;
overflow-y: auto;
scrollbar-width: none;
+ -ms-overflow-style: none;
&::-webkit-scrollbar {
display: none;
@@ -21,6 +22,7 @@
.nav-tab-button {
width: calc(var(--vertical-nav-bar-size) - 1.2rem);
height: calc(var(--vertical-nav-bar-size) - 1.2rem);
+ min-height: 3.5rem;
}
}
diff --git a/src/components/Slider/styles.less b/src/components/Slider/styles.less
index 1c22685c8..3acf79e62 100644
--- a/src/components/Slider/styles.less
+++ b/src/components/Slider/styles.less
@@ -39,7 +39,8 @@ html.active-slider-within {
flex: 1;
height: var(--track-size);
border-radius: var(--track-size);
- background-color: var(--overlay-color);
+ background-color: var(--primary-accent-color);
+ opacity: 0.2;
&.audio-boost {
background: linear-gradient(to right,
diff --git a/src/components/index.ts b/src/components/index.ts
index f65d66f81..7ef75f888 100644
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -1,6 +1,7 @@
import AddonDetailsModal from './AddonDetailsModal';
import BottomSheet from './BottomSheet';
import Button from './Button';
+import Checkbox from './Checkbox';
import Chips from './Chips';
import ColorInput from './ColorInput';
import ContinueWatchingItem from './ContinueWatchingItem';
@@ -31,6 +32,7 @@ export {
AddonDetailsModal,
BottomSheet,
Button,
+ Checkbox,
Chips,
ColorInput,
ContinueWatchingItem,
diff --git a/src/routes/Calendar/List/Item/Item.less b/src/routes/Calendar/List/Item/Item.less
index aba6bba09..b11feda2a 100644
--- a/src/routes/Calendar/List/Item/Item.less
+++ b/src/routes/Calendar/List/Item/Item.less
@@ -95,4 +95,4 @@
&:not(.active):hover {
border-color: var(--overlay-color);
}
-}
+}
\ No newline at end of file
diff --git a/src/routes/Calendar/List/List.less b/src/routes/Calendar/List/List.less
index f63078680..9f2dfd774 100644
--- a/src/routes/Calendar/List/List.less
+++ b/src/routes/Calendar/List/List.less
@@ -10,6 +10,10 @@
width: 20rem;
padding: 0 1rem;
overflow-y: auto;
+
+ @supports (scroll-padding-block-start: 0.15rem) {
+ scroll-padding-block-start: 0.15rem;
+ }
}
@media only screen and (max-width: @small) and (orientation: portrait) {
@@ -34,4 +38,4 @@
.list {
display: none;
}
-}
+}
\ No newline at end of file
diff --git a/src/routes/Intro/ConsentToggle/ConsentToggle.js b/src/routes/Intro/ConsentToggle/ConsentToggle.js
deleted file mode 100644
index 9a0210607..000000000
--- a/src/routes/Intro/ConsentToggle/ConsentToggle.js
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright (C) 2017-2023 Smart code 203358507
-
-const React = require('react');
-const PropTypes = require('prop-types');
-const classnames = require('classnames');
-const { Button, Toggle } = require('stremio/components');
-const styles = require('./styles');
-
-const ConsentToggle = React.forwardRef(({ className, label, link, href, onToggle, ...props }, ref) => {
- const toggleOnClick = React.useCallback((event) => {
- if (typeof props.onClick === 'function') {
- props.onClick(event);
- }
-
- if (!event.nativeEvent.togglePrevented && typeof onToggle === 'function') {
- onToggle({
- type: 'toggle',
- reactEvent: event,
- nativeEvent: event.nativeEvent
- });
- }
- }, [onToggle, props.onClick]);
- const linkOnClick = React.useCallback((event) => {
- event.nativeEvent.togglePrevented = true;
- }, []);
- return (
-
-
- {label}
- {' '}
- {
- typeof link === 'string' && link.length > 0 && typeof href === 'string' && href.length > 0 ?
-
- :
- null
- }
-
-
- );
-});
-
-ConsentToggle.displayName = 'ConsentToggle';
-
-ConsentToggle.propTypes = {
- className: PropTypes.string,
- checked: PropTypes.bool,
- label: PropTypes.string,
- link: PropTypes.string,
- href: PropTypes.string,
- onToggle: PropTypes.func,
- onClick: PropTypes.func
-};
-
-module.exports = ConsentToggle;
diff --git a/src/routes/Intro/ConsentToggle/index.js b/src/routes/Intro/ConsentToggle/index.js
deleted file mode 100644
index 8edfe4a27..000000000
--- a/src/routes/Intro/ConsentToggle/index.js
+++ /dev/null
@@ -1,5 +0,0 @@
-// Copyright (C) 2017-2023 Smart code 203358507
-
-const ConsentToggle = require('./ConsentToggle');
-
-module.exports = ConsentToggle;
diff --git a/src/routes/Intro/ConsentToggle/styles.less b/src/routes/Intro/ConsentToggle/styles.less
deleted file mode 100644
index e8229e244..000000000
--- a/src/routes/Intro/ConsentToggle/styles.less
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright (C) 2017-2023 Smart code 203358507
-
-@import (reference) '~@stremio/stremio-colors/less/stremio-colors.less';
-
-:import('~stremio/components/Toggle/styles.less') {
- checkbox-icon: icon;
-}
-
-.consent-toggle-container {
- display: flex;
- flex-direction: row;
- align-items: center;
- padding: 0.5rem 0;
- border-radius: var(--border-radius);
-
- &:focus {
- outline: none;
- background-color: var(--overlay-color);
- }
-
- &:global(.checked) {
- .label {
- opacity: 1;
- }
- }
-
- .label {
- flex: 1;
- margin-left: 1rem;
- font-size: 0.9rem;
- color: var(--primary-foreground-color);
- opacity: 0.6;
-
- .link {
- font-size: 0.9rem;
- color: var(--primary-accent-color);
-
- &:hover {
- text-decoration: underline;
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/routes/Intro/Intro.js b/src/routes/Intro/Intro.js
index fc98fe5cf..989e6febd 100644
--- a/src/routes/Intro/Intro.js
+++ b/src/routes/Intro/Intro.js
@@ -8,9 +8,8 @@ const { default: Icon } = require('@stremio/stremio-icons/react');
const { Modal, useRouteFocused } = require('stremio-router');
const { useServices } = require('stremio/services');
const { useBinaryState } = require('stremio/common');
-const { Button, Image } = require('stremio/components');
+const { Button, Image, Checkbox } = require('stremio/components');
const CredentialsTextInput = require('./CredentialsTextInput');
-const ConsentToggle = require('./ConsentToggle');
const PasswordResetModal = require('./PasswordResetModal');
const useFacebookLogin = require('./useFacebookLogin');
const styles = require('./styles');
@@ -308,30 +307,27 @@ const Intro = ({ queryParams }) => {
onChange={confirmPasswordOnChange}
onSubmit={confirmPasswordOnSubmit}
/>
-
-
-
:
diff --git a/src/routes/Player/Player.js b/src/routes/Player/Player.js
index 1df52a9ac..c491fde7e 100644
--- a/src/routes/Player/Player.js
+++ b/src/routes/Player/Player.js
@@ -8,7 +8,7 @@ const langs = require('langs');
const { useTranslation } = require('react-i18next');
const { useRouteFocused } = require('stremio-router');
const { useServices } = require('stremio/services');
-const { useFullscreen, useBinaryState, useToast, useStreamingServer, withCoreSuspender } = require('stremio/common');
+const { onFileDrop, useFullscreen, useBinaryState, useToast, useStreamingServer, withCoreSuspender, CONSTANTS } = require('stremio/common');
const { HorizontalNavBar, Transition } = require('stremio/components');
const BufferingLoader = require('./BufferingLoader');
const VolumeChangeIndicator = require('./VolumeChangeIndicator');
@@ -133,11 +133,20 @@ const Player = ({ urlParams, queryParams }) => {
toast.show({
type: 'success',
title: t('PLAYER_SUBTITLES_LOADED'),
- message: track.exclusive ? t('PLAYER_SUBTITLES_LOADED_EXCLUSIVE') : t('PLAYER_SUBTITLES_LOADED_ORIGIN', { origin: track.origin }),
+ message:
+ track.exclusive ? t('PLAYER_SUBTITLES_LOADED_EXCLUSIVE') :
+ track.local ? t('PLAYER_SUBTITLES_LOADED_LOCAL') :
+ t('PLAYER_SUBTITLES_LOADED_ORIGIN', { origin: track.origin }),
timeout: 3000
});
}, []);
+ const onExtraSubtitlesTrackAdded = React.useCallback((track) => {
+ if (track.local) {
+ video.setExtraSubtitlesTrack(track.id);
+ }
+ }, []);
+
const onPlayRequested = React.useCallback(() => {
video.setProp('paused', false);
setSeeking(false);
@@ -172,13 +181,11 @@ const Player = ({ urlParams, queryParams }) => {
}, []);
const onSubtitlesTrackSelected = React.useCallback((id) => {
- video.setProp('selectedSubtitlesTrackId', id);
- video.setProp('selectedExtraSubtitlesTrackId', null);
+ video.setSubtitlesTrack(id);
}, []);
const onExtraSubtitlesTrackSelected = React.useCallback((id) => {
- video.setProp('selectedSubtitlesTrackId', null);
- video.setProp('selectedExtraSubtitlesTrackId', id);
+ video.setExtraSubtitlesTrack(id);
}, []);
const onAudioTrackSelected = React.useCallback((id) => {
@@ -270,6 +277,10 @@ const Player = ({ urlParams, queryParams }) => {
event.nativeEvent.immersePrevented = true;
}, []);
+ onFileDrop(CONSTANTS.SUPPORTED_LOCAL_SUBTITLES, async (filename, buffer) => {
+ video.addLocalSubtitles(filename, buffer);
+ });
+
React.useEffect(() => {
setError(null);
video.unload();
@@ -296,6 +307,7 @@ const Player = ({ urlParams, queryParams }) => {
0,
forceTranscoding: forceTranscoding || casting,
maxAudioChannels: settings.surroundSound ? 32 : 2,
+ hardwareDecoding: settings.hardwareDecoding,
streamingServerURL: streamingServer.baseUrl ?
casting ?
streamingServer.baseUrl
@@ -303,7 +315,7 @@ const Player = ({ urlParams, queryParams }) => {
streamingServer.selected.transportUrl
:
null,
- seriesInfo: player.seriesInfo
+ seriesInfo: player.seriesInfo,
}, {
chromecastTransport: chromecast.active ? chromecast.transport : null,
shellTransport: shell.active ? shell.transport : null,
@@ -586,6 +598,7 @@ const Player = ({ urlParams, queryParams }) => {
video.events.on('ended', onEnded);
video.events.on('subtitlesTrackLoaded', onSubtitlesTrackLoaded);
video.events.on('extraSubtitlesTrackLoaded', onExtraSubtitlesTrackLoaded);
+ video.events.on('extraSubtitlesTrackAdded', onExtraSubtitlesTrackAdded);
video.events.on('implementationChanged', onImplementationChanged);
return () => {
@@ -593,6 +606,7 @@ const Player = ({ urlParams, queryParams }) => {
video.events.off('ended', onEnded);
video.events.off('subtitlesTrackLoaded', onSubtitlesTrackLoaded);
video.events.off('extraSubtitlesTrackLoaded', onExtraSubtitlesTrackLoaded);
+ video.events.off('extraSubtitlesTrackAdded', onExtraSubtitlesTrackAdded);
video.events.off('implementationChanged', onImplementationChanged);
};
}, []);
diff --git a/src/routes/Player/SubtitlesMenu/SubtitlesMenu.js b/src/routes/Player/SubtitlesMenu/SubtitlesMenu.js
index 44cd20864..39bc771e6 100644
--- a/src/routes/Player/SubtitlesMenu/SubtitlesMenu.js
+++ b/src/routes/Player/SubtitlesMenu/SubtitlesMenu.js
@@ -10,11 +10,13 @@ const styles = require('./styles');
const { t } = require('i18next');
const ORIGIN_PRIORITIES = {
+ 'LOCAL': 3,
'EMBEDDED': 2,
- 'EXCLUSIVE': 1
+ 'EXCLUSIVE': 1,
};
const LANGUAGE_PRIORITIES = {
- 'eng': 1
+ 'local': 2,
+ 'eng': 1,
};
const SubtitlesMenu = React.memo((props) => {
@@ -161,7 +163,11 @@ const SubtitlesMenu = React.memo((props) => {
{subtitlesLanguages.map((lang, index) => (