diff --git a/src/App/App.js b/src/App/App.js
index 85697ddca..0e5bb3fe1 100644
--- a/src/App/App.js
+++ b/src/App/App.js
@@ -5,9 +5,9 @@ const React = require('react');
const { useTranslation } = require('react-i18next');
const { useCore } = require('stremio/core');
const { Router } = require('stremio-router');
-const { Shell, Chromecast, DragAndDrop, KeyboardShortcuts, ServicesProvider, GamepadProvider } = require('stremio/services');
+const { Shell, Chromecast, KeyboardShortcuts, ServicesProvider, GamepadProvider } = require('stremio/services');
const { NotFound } = require('stremio/routes');
-const { FileDropProvider, FullscreenProvider, PlatformProvider, ToastProvider, TooltipProvider, ShortcutsProvider, CONSTANTS, useShell, useBinaryState, useProfile, withCoreSuspender } = require('stremio/common');
+const { FullscreenProvider, PlatformProvider, ToastProvider, TooltipProvider, ShortcutsProvider, CONSTANTS, useShell, useBinaryState, useProfile, withCoreSuspender, onFileDrop } = require('stremio/common');
const ServicesToaster = require('./ServicesToaster');
const DeepLinkHandler = require('./DeepLinkHandler');
const SearchParamsHandler = require('./SearchParamsHandler');
@@ -34,7 +34,6 @@ const App = () => {
shell: new Shell(),
chromecast: new Chromecast(),
keyboardShortcuts: new KeyboardShortcuts(),
- dragAndDrop: new DragAndDrop({ core })
};
}, []);
const [shortcutModalOpen,, closeShortcutsModal, toggleShortcutModal] = useBinaryState(false);
@@ -51,6 +50,16 @@ const App = () => {
}
}, [toggleShortcutModal, toggleGamepadModal]);
+ onFileDrop(['application/x-bittorrent'], (file, buffer) => {
+ core.transport.dispatch({
+ action: 'StreamingServer',
+ args: {
+ action: 'CreateTorrent',
+ args: Array.from(new Uint8Array(buffer))
+ }
+ });
+ });
+
React.useEffect(() => {
let prevPath = window.location.hash.slice(1);
const onLocationHashChange = () => {
@@ -82,13 +91,11 @@ const App = () => {
services.shell.start();
services.chromecast.start();
services.keyboardShortcuts.start();
- services.dragAndDrop.start();
window.services = services;
return () => {
services.shell.stop();
services.chromecast.stop();
services.keyboardShortcuts.stop();
- services.dragAndDrop.stop();
services.chromecast.off('stateChanged', onChromecastStateChange);
};
}, []);
@@ -174,29 +181,27 @@ const App = () => {
-
-
-
-
- {
- shortcutModalOpen &&
- }
- {
- gamepadModalOpen &&
- }
-
-
-
-
-
-
-
-
-
+
+
+
+ {
+ shortcutModalOpen &&
+ }
+ {
+ gamepadModalOpen &&
+ }
+
+
+
+
+
+
+
+
diff --git a/src/App/ServicesToaster.js b/src/App/ServicesToaster.js
index 6338fba00..129a25131 100644
--- a/src/App/ServicesToaster.js
+++ b/src/App/ServicesToaster.js
@@ -1,14 +1,14 @@
// Copyright (C) 2017-2023 Smart code 203358507
const React = require('react');
-const { useServices } = require('stremio/services');
const { useCore } = require('stremio/core');
-const { useToast } = require('stremio/common');
+const { useToast, useFileDrop } = require('stremio/common');
const ServicesToaster = () => {
- const { dragAndDrop } = useServices();
const core = useCore();
const toast = useToast();
+ const filedrop = useFileDrop();
+
React.useEffect(() => {
const onCoreEvent = (name, data) => {
switch (name) {
@@ -53,21 +53,23 @@ const ServicesToaster = () => {
}
});
};
- const onDragAndDropError = (error) => {
- toast.show({
- type: 'error',
- title: error.message,
- message: error.file?.name,
- timeout: 4000
- });
+ const onFileDrop = (file, buffer, supported) => {
+ if (!supported) {
+ toast.show({
+ type: 'error',
+ title: 'Unsupported file',
+ message: file.name,
+ timeout: 4000
+ });
+ }
};
core.on('event', onCoreEvent);
core.on('error', onCoreError);
- dragAndDrop.on('error', onDragAndDropError);
+ filedrop.on('*', onFileDrop);
return () => {
core.off('event', onCoreEvent);
core.off('error', onCoreError);
- dragAndDrop.off('error', onDragAndDropError);
+ filedrop.off('*', onFileDrop);
};
}, []);
return null;
diff --git a/src/App/styles.less b/src/App/styles.less
index dc6aa512f..e12d4bbc0 100644
--- a/src/App/styles.less
+++ b/src/App/styles.less
@@ -213,22 +213,6 @@ html {
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) {
- border-color: var(--primary-accent-color);
- }
- }
-
.updater-banner-container {
z-index: 1;
position: absolute;
diff --git a/src/common/CONSTANTS.js b/src/common/CONSTANTS.js
index 104d24a44..85b2ada08 100644
--- a/src/common/CONSTANTS.js
+++ b/src/common/CONSTANTS.js
@@ -46,6 +46,7 @@ const ICON_FOR_TYPE = new Map([
const MIME_SIGNATURES = {
'application/x-subrip': ['310D0A', '310A'],
'text/vtt': ['574542565454'],
+ 'application/x-bittorrent': ['64'],
};
const SUPPORTED_LOCAL_SUBTITLES = [
diff --git a/src/common/FileDrop/FileDrop.tsx b/src/common/FileDrop/FileDrop.tsx
index 2993991e7..008adb6a5 100644
--- a/src/common/FileDrop/FileDrop.tsx
+++ b/src/common/FileDrop/FileDrop.tsx
@@ -1,9 +1,10 @@
-import React, { createContext, useCallback, useContext, useEffect, useState } from 'react';
+import React, { createContext, useContext, useEffect, useRef, useState } from 'react';
import classNames from 'classnames';
-import { isFileType } from './utils';
+import { isFileType, isFileTypeSupported } from './utils';
+import styles from './styles.less';
export type FileType = string;
-export type FileDropListener = (filename: string, buffer: ArrayBuffer) => void;
+export type FileDropListener = (file: File, buffer: ArrayBuffer, supported: boolean) => void;
type FileDropContext = {
on: (type: FileType, listener: FileDropListener) => void,
@@ -13,12 +14,11 @@ type FileDropContext = {
const FileDropContext = createContext({} as FileDropContext);
type Props = {
- className: string,
- children: JSX.Element,
+ children: React.ReactNode,
};
-const FileDropProvider = ({ className, children }: Props) => {
- const [listeners, setListeners] = useState<[FileType, FileDropListener][]>([]);
+const FileDropProvider = ({ children }: Props) => {
+ const listeners = useRef<[FileType, FileDropListener][]>([]);
const [active, setActive] = useState(false);
const onDragOver = (event: DragEvent) => {
@@ -30,38 +30,38 @@ const FileDropProvider = ({ className, children }: Props) => {
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(([, listener]) => listener(file.name, buffer));
- });
- }
-
- setActive(false);
- }, [listeners]);
-
const on = (type: FileType, listener: FileDropListener) => {
- setListeners((listeners) => {
- return [...listeners, [type, listener]];
- });
+ listeners.current = [...listeners.current, [type, listener]];
};
const off = (type: FileType, listener: FileDropListener) => {
- setListeners((listeners) => {
- return listeners.filter(([key, value]) => key !== type && value !== listener);
- });
+ listeners.current = listeners.current.filter(([key, value]) => key !== type && value !== listener);
};
useEffect(() => {
+ const onDrop = (event: DragEvent) => {
+ event.preventDefault();
+ const { dataTransfer } = event;
+
+ if (dataTransfer && dataTransfer?.files.length > 0) {
+ const file = dataTransfer.files[0];
+
+ file
+ .arrayBuffer()
+ .then((buffer) => {
+ listeners.current
+ .filter(([type]) => type === '*')
+ .forEach(([, listener]) => listener(file, buffer, isFileTypeSupported(buffer)));
+ listeners.current
+ .filter(([type]) => type !== '*' && (file.type ? type === file.type : isFileType(buffer, type)))
+ .forEach(([, listener]) => listener(file, buffer, true));
+ })
+ .catch(console.error);
+ }
+
+ setActive(false);
+ };
+
window.addEventListener('dragover', onDragOver);
window.addEventListener('dragleave', onDragLeave);
window.addEventListener('drop', onDrop);
@@ -71,12 +71,12 @@ const FileDropProvider = ({ className, children }: Props) => {
window.removeEventListener('dragleave', onDragLeave);
window.removeEventListener('drop', onDrop);
};
- }, [onDrop]);
+ }, []);
return (
{ children }
-
+
);
};
diff --git a/src/common/FileDrop/onFileDrop.ts b/src/common/FileDrop/onFileDrop.ts
index 339b0219b..2eb9ea40a 100644
--- a/src/common/FileDrop/onFileDrop.ts
+++ b/src/common/FileDrop/onFileDrop.ts
@@ -6,7 +6,6 @@ const onFileDrop = (types: FileType[], listener: FileDropListener) => {
useEffect(() => {
types.forEach((type) => on(type, listener));
-
return () => types.forEach((type) => off(type, listener));
}, []);
};
diff --git a/src/common/FileDrop/styles.less b/src/common/FileDrop/styles.less
new file mode 100644
index 000000000..6a538a4fe
--- /dev/null
+++ b/src/common/FileDrop/styles.less
@@ -0,0 +1,15 @@
+.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) {
+ border-color: var(--primary-accent-color);
+ }
+}
\ No newline at end of file
diff --git a/src/common/FileDrop/utils.ts b/src/common/FileDrop/utils.ts
index f9996aed3..2fa69bbf7 100644
--- a/src/common/FileDrop/utils.ts
+++ b/src/common/FileDrop/utils.ts
@@ -14,6 +14,11 @@ const isFileType = (buffer: ArrayBuffer, type: string) => {
});
};
+const isFileTypeSupported = (buffer: ArrayBuffer) => {
+ return Object.keys(SIGNATURES).some((type) => isFileType(buffer, type));
+};
+
export {
isFileType,
+ isFileTypeSupported,
};
diff --git a/src/common/index.js b/src/common/index.js
index 1963e8995..c27b37178 100644
--- a/src/common/index.js
+++ b/src/common/index.js
@@ -1,6 +1,6 @@
// Copyright (C) 2017-2023 Smart code 203358507
-const { FileDropProvider, onFileDrop } = require('./FileDrop');
+const { FileDropProvider, useFileDrop, onFileDrop } = require('./FileDrop');
const { FullscreenProvider, useFullscreen } = require('./Fullscreen');
const { PlatformProvider, usePlatform } = require('./Platform');
const { ToastProvider, useToast } = require('./Toast');
@@ -33,6 +33,7 @@ const { default: useLanguageSorting } = require('./useLanguageSorting');
module.exports = {
FileDropProvider,
+ useFileDrop,
onFileDrop,
FullscreenProvider,
PlatformProvider,
diff --git a/src/index.js b/src/index.js
index e9061296c..74d39601d 100755
--- a/src/index.js
+++ b/src/index.js
@@ -18,6 +18,7 @@ const { initReactI18next } = require('react-i18next');
const stremioTranslations = require('stremio-translations');
const App = require('./App');
const { CoreProvider } = require('./core');
+const { FileDropProvider } = require('./common');
const translations = Object.fromEntries(Object.entries(stremioTranslations()).map(([key, value]) => [key, {
translation: value
@@ -42,7 +43,9 @@ const appInfo = {
const root = ReactDOM.createRoot(document.getElementById('app'));
root.render(
-
+
+
+
);
diff --git a/src/routes/Player/useSubtitles.ts b/src/routes/Player/useSubtitles.ts
index 09fb6be78..26de76d74 100644
--- a/src/routes/Player/useSubtitles.ts
+++ b/src/routes/Player/useSubtitles.ts
@@ -151,8 +151,8 @@ const useSubtitles = ({
streamStateChanged({ subtitleOffset: offset });
}, [streamStateChanged, video]);
- onFileDrop(CONSTANTS.SUPPORTED_LOCAL_SUBTITLES, (filename: string, buffer: ArrayBuffer) => {
- videoRef.current.addLocalSubtitles(filename, buffer);
+ onFileDrop(CONSTANTS.SUPPORTED_LOCAL_SUBTITLES, (file: File, buffer: ArrayBuffer) => {
+ videoRef.current.addLocalSubtitles(file.name, buffer);
});
useEffect(() => {
diff --git a/src/services/DragAndDrop/DragAndDrop.js b/src/services/DragAndDrop/DragAndDrop.js
deleted file mode 100644
index 1538c328e..000000000
--- a/src/services/DragAndDrop/DragAndDrop.js
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright (C) 2017-2023 Smart code 203358507
-
-const EventEmitter = require('eventemitter3');
-
-function DragAndDrop({ core }) {
- let active = false;
-
- const events = new EventEmitter();
-
- function onDragOver(event) {
- event.preventDefault();
- }
- async function onDrop(event) {
- event.preventDefault();
- if (event.dataTransfer.files instanceof FileList && event.dataTransfer.files.length > 0) {
- const file = event.dataTransfer.files[0];
- switch (file.type) {
- case 'application/x-bittorrent': {
- try {
- const torrent = await file.arrayBuffer();
- core.transport.dispatch({
- action: 'StreamingServer',
- args: {
- action: 'CreateTorrent',
- args: Array.from(new Uint8Array(torrent))
- }
- });
- } catch (_error) {
- events.emit('error', {
- message: 'Failed to process file',
- file: {
- name: file.name,
- type: file.type
- }
- });
- }
- break;
- }
- case 'application/x-subrip':
- break;
- case 'text/vtt':
- break;
- case '':
- break;
- default: {
- events.emit('error', {
- message: 'Unsupported file',
- file: {
- name: file.name,
- type: file.type
- }
- });
- }
- }
- }
- }
- function onStateChanged() {
- events.emit('stateChanged');
- }
-
- Object.defineProperties(this, {
- active: {
- configurable: false,
- enumerable: true,
- get: function() {
- return active;
- }
- }
- });
-
- this.start = function() {
- if (active) {
- return;
- }
-
- window.addEventListener('dragover', onDragOver);
- window.addEventListener('drop', onDrop);
- active = true;
- onStateChanged();
- };
- this.stop = function() {
- window.removeEventListener('dragover', onDragOver);
- window.removeEventListener('drop', onDrop);
- active = false;
- onStateChanged();
- };
- this.on = function(name, listener) {
- events.on(name, listener);
- };
- this.off = function(name, listener) {
- events.off(name, listener);
- };
-}
-
-module.exports = DragAndDrop;
diff --git a/src/services/DragAndDrop/index.js b/src/services/DragAndDrop/index.js
deleted file mode 100644
index 5fb7baf15..000000000
--- a/src/services/DragAndDrop/index.js
+++ /dev/null
@@ -1,5 +0,0 @@
-// Copyright (C) 2017-2023 Smart code 203358507
-
-const DragAndDrop = require('./DragAndDrop');
-
-module.exports = DragAndDrop;
diff --git a/src/services/ServicesContext/types.d.ts b/src/services/ServicesContext/types.d.ts
index 45dde7ed9..49b80525b 100644
--- a/src/services/ServicesContext/types.d.ts
+++ b/src/services/ServicesContext/types.d.ts
@@ -2,5 +2,4 @@ type ServicesContext = {
shell: any,
chromecast: any,
keyboardShortcuts: any,
- dragAndDrop: any,
};
diff --git a/src/services/index.js b/src/services/index.js
index 17f00719d..aede80a89 100644
--- a/src/services/index.js
+++ b/src/services/index.js
@@ -1,7 +1,6 @@
// Copyright (C) 2017-2023 Smart code 203358507
const Chromecast = require('./Chromecast');
-const DragAndDrop = require('./DragAndDrop');
const KeyboardShortcuts = require('./KeyboardShortcuts');
const { ServicesProvider, useServices } = require('./ServicesContext');
const { GamepadProvider, useGamepad } = require('./GamepadContext');
@@ -9,7 +8,6 @@ const Shell = require('./Shell');
module.exports = {
Chromecast,
- DragAndDrop,
KeyboardShortcuts,
ServicesProvider,
useServices,