mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-04-21 07:32:02 +00:00
Merge bbbf505e10 into 5dc088b798
This commit is contained in:
commit
9916a55bd8
10 changed files with 236 additions and 3 deletions
|
|
@ -4,7 +4,7 @@ require('spatial-navigation-polyfill');
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
const { useTranslation } = require('react-i18next');
|
const { useTranslation } = require('react-i18next');
|
||||||
const { Router } = require('stremio-router');
|
const { Router } = require('stremio-router');
|
||||||
const { Core, Shell, Chromecast, DragAndDrop, KeyboardShortcuts, ServicesProvider } = require('stremio/services');
|
const { Core, Shell, Chromecast, Discord, DragAndDrop, KeyboardShortcuts, ServicesProvider } = require('stremio/services');
|
||||||
const { NotFound } = require('stremio/routes');
|
const { NotFound } = require('stremio/routes');
|
||||||
const { FileDropProvider, PlatformProvider, ToastProvider, TooltipProvider, ShortcutsProvider, CONSTANTS, withCoreSuspender, useShell, useBinaryState } = require('stremio/common');
|
const { FileDropProvider, PlatformProvider, ToastProvider, TooltipProvider, ShortcutsProvider, CONSTANTS, withCoreSuspender, useShell, useBinaryState } = require('stremio/common');
|
||||||
const ServicesToaster = require('./ServicesToaster');
|
const ServicesToaster = require('./ServicesToaster');
|
||||||
|
|
@ -33,6 +33,7 @@ const App = () => {
|
||||||
return {
|
return {
|
||||||
core,
|
core,
|
||||||
shell: new Shell(),
|
shell: new Shell(),
|
||||||
|
discord: new Discord(),
|
||||||
chromecast: new Chromecast(),
|
chromecast: new Chromecast(),
|
||||||
keyboardShortcuts: new KeyboardShortcuts(),
|
keyboardShortcuts: new KeyboardShortcuts(),
|
||||||
dragAndDrop: new DragAndDrop({ core })
|
dragAndDrop: new DragAndDrop({ core })
|
||||||
|
|
@ -95,6 +96,8 @@ const App = () => {
|
||||||
services.chromecast.start();
|
services.chromecast.start();
|
||||||
services.keyboardShortcuts.start();
|
services.keyboardShortcuts.start();
|
||||||
services.dragAndDrop.start();
|
services.dragAndDrop.start();
|
||||||
|
services.discord.init(services.shell);
|
||||||
|
|
||||||
window.services = services;
|
window.services = services;
|
||||||
return () => {
|
return () => {
|
||||||
services.core.stop();
|
services.core.stop();
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ const useAnimationFrame = require('./useAnimationFrame');
|
||||||
const useBinaryState = require('./useBinaryState');
|
const useBinaryState = require('./useBinaryState');
|
||||||
const { default: useFullscreen } = require('./useFullscreen');
|
const { default: useFullscreen } = require('./useFullscreen');
|
||||||
const { default: useInterval } = require('./useInterval');
|
const { default: useInterval } = require('./useInterval');
|
||||||
|
const { default: useDiscord } = require('./useDiscord');
|
||||||
const useLiveRef = require('./useLiveRef');
|
const useLiveRef = require('./useLiveRef');
|
||||||
const useModelState = require('./useModelState');
|
const useModelState = require('./useModelState');
|
||||||
const useNotifications = require('./useNotifications');
|
const useNotifications = require('./useNotifications');
|
||||||
|
|
@ -55,6 +56,7 @@ module.exports = {
|
||||||
useBinaryState,
|
useBinaryState,
|
||||||
useFullscreen,
|
useFullscreen,
|
||||||
useInterval,
|
useInterval,
|
||||||
|
useDiscord,
|
||||||
useLiveRef,
|
useLiveRef,
|
||||||
useModelState,
|
useModelState,
|
||||||
useNotifications,
|
useNotifications,
|
||||||
|
|
|
||||||
83
src/common/useDiscord.ts
Normal file
83
src/common/useDiscord.ts
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
// Copyright (C) 2017-2025 Smart code 203358507
|
||||||
|
|
||||||
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
import { useServices } from 'stremio/services';
|
||||||
|
|
||||||
|
type Service = {
|
||||||
|
available: boolean;
|
||||||
|
connected: boolean;
|
||||||
|
connect: () => void;
|
||||||
|
disconnect: () => void;
|
||||||
|
setActivity: (state: string, details: string, image?: string, startTimestamp?: number) => void;
|
||||||
|
clearActivity: () => void;
|
||||||
|
on: (name: string, listener: (data: unknown) => void) => void;
|
||||||
|
off: (name: string, listener: (data: unknown) => void) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Result = {
|
||||||
|
available: boolean;
|
||||||
|
connected: boolean;
|
||||||
|
connect: () => void;
|
||||||
|
disconnect: () => void;
|
||||||
|
setActivity: (state: string, details: string, image?: string, startTimestamp?: number) => void;
|
||||||
|
clearActivity: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const useDiscord = (): Result => {
|
||||||
|
const { discord } = useServices() as { discord?: Service };
|
||||||
|
const [connected, setConnected] = useState(discord?.connected ?? false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!discord) return;
|
||||||
|
|
||||||
|
const onStatusChanged = (isConnected: boolean) => {
|
||||||
|
setConnected(isConnected);
|
||||||
|
};
|
||||||
|
|
||||||
|
discord.on('statusChanged', onStatusChanged as (data: unknown) => void);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
discord.off('statusChanged', onStatusChanged as (data: unknown) => void);
|
||||||
|
};
|
||||||
|
}, [discord]);
|
||||||
|
|
||||||
|
const connect = useCallback(() => {
|
||||||
|
if (discord) {
|
||||||
|
discord.connect();
|
||||||
|
}
|
||||||
|
}, [discord]);
|
||||||
|
|
||||||
|
const disconnect = useCallback(() => {
|
||||||
|
if (discord) {
|
||||||
|
discord.disconnect();
|
||||||
|
}
|
||||||
|
}, [discord]);
|
||||||
|
|
||||||
|
const setActivity = useCallback((
|
||||||
|
state: string,
|
||||||
|
details: string,
|
||||||
|
image?: string,
|
||||||
|
startTimestamp?: number
|
||||||
|
) => {
|
||||||
|
if (discord) {
|
||||||
|
discord.setActivity(state, details, image, startTimestamp);
|
||||||
|
}
|
||||||
|
}, [discord]);
|
||||||
|
|
||||||
|
const clearActivity = useCallback(() => {
|
||||||
|
if (discord) {
|
||||||
|
discord.clearActivity();
|
||||||
|
}
|
||||||
|
}, [discord]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
available: discord?.available ?? false,
|
||||||
|
connected,
|
||||||
|
connect,
|
||||||
|
disconnect,
|
||||||
|
setActivity,
|
||||||
|
clearActivity
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useDiscord;
|
||||||
|
|
@ -8,7 +8,7 @@ const langs = require('langs');
|
||||||
const { useTranslation } = require('react-i18next');
|
const { useTranslation } = require('react-i18next');
|
||||||
const { useRouteFocused } = require('stremio-router');
|
const { useRouteFocused } = require('stremio-router');
|
||||||
const { useServices } = require('stremio/services');
|
const { useServices } = require('stremio/services');
|
||||||
const { onFileDrop, useSettings, useProfile, useFullscreen, useBinaryState, useToast, useStreamingServer, withCoreSuspender, CONSTANTS, useShell, usePlatform } = require('stremio/common');
|
const { onFileDrop, useSettings, useProfile, useFullscreen, useBinaryState, useToast, useStreamingServer, withCoreSuspender, CONSTANTS, useShell, usePlatform, useDiscord } = require('stremio/common');
|
||||||
const { HorizontalNavBar, Transition, ContextMenu } = require('stremio/components');
|
const { HorizontalNavBar, Transition, ContextMenu } = require('stremio/components');
|
||||||
const BufferingLoader = require('./BufferingLoader');
|
const BufferingLoader = require('./BufferingLoader');
|
||||||
const VolumeChangeIndicator = require('./VolumeChangeIndicator');
|
const VolumeChangeIndicator = require('./VolumeChangeIndicator');
|
||||||
|
|
@ -45,6 +45,7 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
const routeFocused = useRouteFocused();
|
const routeFocused = useRouteFocused();
|
||||||
const platform = usePlatform();
|
const platform = usePlatform();
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
const discord = useDiscord();
|
||||||
|
|
||||||
const [seeking, setSeeking] = React.useState(false);
|
const [seeking, setSeeking] = React.useState(false);
|
||||||
|
|
||||||
|
|
@ -550,6 +551,22 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
}
|
}
|
||||||
}, [settings.pauseOnMinimize, shell.windowClosed, shell.windowHidden]);
|
}, [settings.pauseOnMinimize, shell.windowClosed, shell.windowHidden]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!discord.connected || !discord.available) return;
|
||||||
|
|
||||||
|
const state = video.state.paused ? 'Paused' : 'Watching';
|
||||||
|
|
||||||
|
const startTimestamp = !video.state.paused && video.state.time !== null && video.state.duration !== null
|
||||||
|
? Math.floor((Date.now() / 1000) - video.state.time)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
discord.setActivity(state, player?.title, player?.metaItem?.poster, startTimestamp);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
discord.clearActivity();
|
||||||
|
};
|
||||||
|
}, [discord.connected, discord.available, player?.title, player?.metaItem, video.state]);
|
||||||
|
|
||||||
// Media Session PlaybackState
|
// Media Session PlaybackState
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!navigator.mediaSession) return;
|
if (!navigator.mediaSession) return;
|
||||||
|
|
|
||||||
|
|
@ -9,3 +9,9 @@
|
||||||
color: var(--color-trakt) !important;
|
color: var(--color-trakt) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.discord-container {
|
||||||
|
.option-icon {
|
||||||
|
color: #5865F2 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import React, { forwardRef, useCallback, useEffect, useMemo, useState } from 're
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Button, MultiselectMenu, Toggle } from 'stremio/components';
|
import { Button, MultiselectMenu, Toggle } from 'stremio/components';
|
||||||
import { useServices } from 'stremio/services';
|
import { useServices } from 'stremio/services';
|
||||||
import { usePlatform, useToast } from 'stremio/common';
|
import { usePlatform, useToast, useDiscord } from 'stremio/common';
|
||||||
import { Section, Option, Link } from '../components';
|
import { Section, Option, Link } from '../components';
|
||||||
import User from './User';
|
import User from './User';
|
||||||
import useDataExport from './useDataExport';
|
import useDataExport from './useDataExport';
|
||||||
|
|
@ -18,6 +18,7 @@ const General = forwardRef<HTMLDivElement, Props>(({ profile }: Props, ref) => {
|
||||||
const { core, shell } = useServices();
|
const { core, shell } = useServices();
|
||||||
const platform = usePlatform();
|
const platform = usePlatform();
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
const { available: discordAvailable, connected: isDiscordConnected, connect: connectDiscord, disconnect: disconnectDiscord } = useDiscord();
|
||||||
const [dataExport, loadDataExport] = useDataExport();
|
const [dataExport, loadDataExport] = useDataExport();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|
@ -69,12 +70,44 @@ const General = forwardRef<HTMLDivElement, Props>(({ profile }: Props, ref) => {
|
||||||
}
|
}
|
||||||
}, [isTraktAuthenticated, profile.auth]);
|
}, [isTraktAuthenticated, profile.auth]);
|
||||||
|
|
||||||
|
const onToggleDiscord = useCallback(() => {
|
||||||
|
if (isDiscordConnected) {
|
||||||
|
disconnectDiscord();
|
||||||
|
core.transport.dispatch({
|
||||||
|
action: 'Ctx',
|
||||||
|
args: {
|
||||||
|
action: 'UpdateSettings',
|
||||||
|
args: {
|
||||||
|
discordRpcEnabled: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
connectDiscord();
|
||||||
|
core.transport.dispatch({
|
||||||
|
action: 'Ctx',
|
||||||
|
args: {
|
||||||
|
action: 'UpdateSettings',
|
||||||
|
args: {
|
||||||
|
discordRpcEnabled: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [isDiscordConnected, connectDiscord, disconnectDiscord]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (dataExport.exportUrl) {
|
if (dataExport.exportUrl) {
|
||||||
platform.openExternal(dataExport.exportUrl);
|
platform.openExternal(dataExport.exportUrl);
|
||||||
}
|
}
|
||||||
}, [dataExport.exportUrl]);
|
}, [dataExport.exportUrl]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (discordAvailable && profile.settings.discordRpcEnabled && !isDiscordConnected) {
|
||||||
|
connectDiscord();
|
||||||
|
}
|
||||||
|
}, [discordAvailable, profile.settings.discordRpcEnabled]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isTraktAuthenticated && traktAuthStarted) {
|
if (isTraktAuthenticated && traktAuthStarted) {
|
||||||
core.transport.dispatch({
|
core.transport.dispatch({
|
||||||
|
|
@ -142,6 +175,14 @@ const General = forwardRef<HTMLDivElement, Props>(({ profile }: Props, ref) => {
|
||||||
{isTraktAuthenticated ? t('LOG_OUT') : t('SETTINGS_TRAKT_AUTHENTICATE')}
|
{isTraktAuthenticated ? t('LOG_OUT') : t('SETTINGS_TRAKT_AUTHENTICATE')}
|
||||||
</Button>
|
</Button>
|
||||||
</Option>
|
</Option>
|
||||||
|
{
|
||||||
|
discordAvailable &&
|
||||||
|
<Option className={styles['discord-container']} icon={'discord'} label={t('SETTINGS_DISCORD')}>
|
||||||
|
<Button className={'button'} title={isDiscordConnected ? t('DISCONNECT') : t('SETTINGS_DISCORD_CONNECT')} tabIndex={-1} onClick={onToggleDiscord}>
|
||||||
|
{isDiscordConnected ? t('DISCONNECT') : t('SETTINGS_DISCORD_CONNECT')}
|
||||||
|
</Button>
|
||||||
|
</Option>
|
||||||
|
}
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
<Section>
|
<Section>
|
||||||
|
|
|
||||||
73
src/services/Discord/Discord.ts
Normal file
73
src/services/Discord/Discord.ts
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
// Copyright (C) 2017-2025 Smart code 203358507
|
||||||
|
|
||||||
|
import EventEmitter from 'eventemitter3';
|
||||||
|
|
||||||
|
type DiscordStatusData = {
|
||||||
|
connected: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Discord {
|
||||||
|
private events: EventEmitter;
|
||||||
|
private connected: boolean;
|
||||||
|
private shell: any;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.events = new EventEmitter();
|
||||||
|
this.connected = false;
|
||||||
|
this.shell = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
init(shellService: any): void {
|
||||||
|
this.shell = shellService;
|
||||||
|
|
||||||
|
if (this.shell && this.shell.transport) {
|
||||||
|
this.shell.transport.on('discord-status', (data: DiscordStatusData) => {
|
||||||
|
this.connected = data.connected;
|
||||||
|
this.events.emit('statusChanged', this.connected);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(): void {
|
||||||
|
if (this.shell && this.shell.active) {
|
||||||
|
this.shell.transport.send('discord-connect', {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnect(): void {
|
||||||
|
if (this.shell && this.shell.active) {
|
||||||
|
this.shell.transport.send('discord-disconnect', {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setActivity(state: string, details: string, image?: string | null, startTimestamp?: number | null): void {
|
||||||
|
if (this.shell && this.shell.active && this.connected) {
|
||||||
|
this.shell.transport.send('discord-set-activity', {
|
||||||
|
state,
|
||||||
|
details,
|
||||||
|
image: image || null,
|
||||||
|
startTimestamp: startTimestamp || null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clearActivity(): void {
|
||||||
|
if (this.shell && this.shell.active && this.connected) {
|
||||||
|
this.shell.transport.send('discord-clear-activity', {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get available(): boolean {
|
||||||
|
return this.shell && this.shell.active;
|
||||||
|
}
|
||||||
|
|
||||||
|
on(name: string, listener: (data: any) => void): void {
|
||||||
|
this.events.on(name, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
off(name: string, listener: (data: any) => void): void {
|
||||||
|
this.events.off(name, listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Discord;
|
||||||
5
src/services/Discord/index.ts
Normal file
5
src/services/Discord/index.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
// Copyright (C) 2017-2025 Smart code 203358507
|
||||||
|
|
||||||
|
import Discord from './Discord';
|
||||||
|
|
||||||
|
export default Discord;
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
const Chromecast = require('./Chromecast');
|
const Chromecast = require('./Chromecast');
|
||||||
const Core = require('./Core');
|
const Core = require('./Core');
|
||||||
|
const Discord = require('./Discord');
|
||||||
const DragAndDrop = require('./DragAndDrop');
|
const DragAndDrop = require('./DragAndDrop');
|
||||||
const KeyboardShortcuts = require('./KeyboardShortcuts');
|
const KeyboardShortcuts = require('./KeyboardShortcuts');
|
||||||
const { ServicesProvider, useServices } = require('./ServicesContext');
|
const { ServicesProvider, useServices } = require('./ServicesContext');
|
||||||
|
|
@ -10,6 +11,7 @@ const Shell = require('./Shell');
|
||||||
module.exports = {
|
module.exports = {
|
||||||
Chromecast,
|
Chromecast,
|
||||||
Core,
|
Core,
|
||||||
|
Discord,
|
||||||
DragAndDrop,
|
DragAndDrop,
|
||||||
KeyboardShortcuts,
|
KeyboardShortcuts,
|
||||||
ServicesProvider,
|
ServicesProvider,
|
||||||
|
|
|
||||||
1
src/types/models/Ctx.d.ts
vendored
1
src/types/models/Ctx.d.ts
vendored
|
|
@ -18,6 +18,7 @@ type Settings = {
|
||||||
audioPassthrough: boolean,
|
audioPassthrough: boolean,
|
||||||
autoFrameRateMatching: boolean,
|
autoFrameRateMatching: boolean,
|
||||||
bingeWatching: boolean,
|
bingeWatching: boolean,
|
||||||
|
discordRpcEnabled: boolean,
|
||||||
hardwareDecoding: boolean,
|
hardwareDecoding: boolean,
|
||||||
videoMode: string | null,
|
videoMode: string | null,
|
||||||
escExitFullscreen: boolean,
|
escExitFullscreen: boolean,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue