mirror of
https://github.com/Andre-Bzk/twitch4steamdeck.git
synced 2026-11-15 15:43:08 +01:00
Phase 6 Fix 4
- Unter "Du folgst" wurde immer der erste Kanal aufgerufen, unabhängig vom ausgewählten Kanal - Abbrechen der Wiedergabe auch unter "Durchsuchen" und Top-Stream möglich.
This commit is contained in:
@@ -123,6 +123,12 @@ class JoystickReader {
|
|||||||
} else if (axis === 1) {
|
} else if (axis === 1) {
|
||||||
if (value < -STICK_THRESHOLD) key = 'ArrowUp'
|
if (value < -STICK_THRESHOLD) key = 'ArrowUp'
|
||||||
else if (value > STICK_THRESHOLD) key = 'ArrowDown'
|
else if (value > STICK_THRESHOLD) key = 'ArrowDown'
|
||||||
|
}
|
||||||
|
// Trigger (Axes 2/5): -32767 = nicht gedrückt, +32767 = voll gedrückt
|
||||||
|
else if (axis === 2) {
|
||||||
|
if (value > 0) key = 'l2'
|
||||||
|
} else if (axis === 5) {
|
||||||
|
if (value > 0) key = 'r2'
|
||||||
} else {
|
} else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export const IPC = {
|
|||||||
playbackStartLive: 'playback:start-live',
|
playbackStartLive: 'playback:start-live',
|
||||||
playbackStartVod: 'playback:start-vod',
|
playbackStartVod: 'playback:start-vod',
|
||||||
playbackStop: 'playback:stop',
|
playbackStop: 'playback:stop',
|
||||||
|
playbackSeek: 'playback:seek',
|
||||||
/** main → renderer */
|
/** main → renderer */
|
||||||
playbackEvent: 'playback:event'
|
playbackEvent: 'playback:event'
|
||||||
} as const
|
} as const
|
||||||
@@ -74,6 +75,7 @@ export function registerIpcHandlers(
|
|||||||
playback.startVod(vodId, channelLogin, title, durationSeconds)
|
playback.startVod(vodId, channelLogin, title, durationSeconds)
|
||||||
)
|
)
|
||||||
ipcMain.handle(IPC.playbackStop, () => playback.stop())
|
ipcMain.handle(IPC.playbackStop, () => playback.stop())
|
||||||
|
ipcMain.handle(IPC.playbackSeek, (_e, seconds: number) => playback.seek(seconds))
|
||||||
|
|
||||||
playback.on('playback-event', (event: PlaybackEvent) => {
|
playback.on('playback-event', (event: PlaybackEvent) => {
|
||||||
for (const win of BrowserWindow.getAllWindows()) {
|
for (const win of BrowserWindow.getAllWindows()) {
|
||||||
|
|||||||
@@ -74,6 +74,12 @@ export class MpvController {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
seek(seconds: number): void {
|
||||||
|
if (this.socket?.writable) {
|
||||||
|
this._send({ command: ['seek', seconds, 'relative'] })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
quit(): void {
|
quit(): void {
|
||||||
if (this.socket?.writable) {
|
if (this.socket?.writable) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -141,6 +141,10 @@ export class PlaybackService extends EventEmitter {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
seek(seconds: number): void {
|
||||||
|
this.current?.mpv.seek(seconds)
|
||||||
|
}
|
||||||
|
|
||||||
async stop(): Promise<void> {
|
async stop(): Promise<void> {
|
||||||
if (!this.current) return
|
if (!this.current) return
|
||||||
const { process: proc, mpv } = this.current
|
const { process: proc, mpv } = this.current
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ const IPC = {
|
|||||||
playbackStartLive: 'playback:start-live',
|
playbackStartLive: 'playback:start-live',
|
||||||
playbackStartVod: 'playback:start-vod',
|
playbackStartVod: 'playback:start-vod',
|
||||||
playbackStop: 'playback:stop',
|
playbackStop: 'playback:stop',
|
||||||
|
playbackSeek: 'playback:seek',
|
||||||
playbackEvent: 'playback:event',
|
playbackEvent: 'playback:event',
|
||||||
|
|
||||||
gamepadInput: 'gamepad-input'
|
gamepadInput: 'gamepad-input'
|
||||||
@@ -114,6 +115,7 @@ const api = {
|
|||||||
startVod: (vodId: string, channelLogin: string, title: string, durationSeconds: number): Promise<void> =>
|
startVod: (vodId: string, channelLogin: string, title: string, durationSeconds: number): Promise<void> =>
|
||||||
ipcRenderer.invoke(IPC.playbackStartVod, vodId, channelLogin, title, durationSeconds),
|
ipcRenderer.invoke(IPC.playbackStartVod, vodId, channelLogin, title, durationSeconds),
|
||||||
stop: (): Promise<void> => ipcRenderer.invoke(IPC.playbackStop),
|
stop: (): Promise<void> => ipcRenderer.invoke(IPC.playbackStop),
|
||||||
|
seek: (seconds: number): Promise<void> => ipcRenderer.invoke(IPC.playbackSeek, seconds),
|
||||||
onEvent: (cb: (event: PlaybackEvent) => void): (() => void) => {
|
onEvent: (cb: (event: PlaybackEvent) => void): (() => void) => {
|
||||||
const listener = (_e: IpcRendererEvent, event: PlaybackEvent): void => cb(event)
|
const listener = (_e: IpcRendererEvent, event: PlaybackEvent): void => cb(event)
|
||||||
ipcRenderer.on(IPC.playbackEvent, listener)
|
ipcRenderer.on(IPC.playbackEvent, listener)
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ const BUTTON_MAP: Record<number, string> = {
|
|||||||
1: 'Escape', // B
|
1: 'Escape', // B
|
||||||
2: 'x', // X
|
2: 'x', // X
|
||||||
3: 'y', // Y
|
3: 'y', // Y
|
||||||
|
6: 'l2', // LT / L2
|
||||||
|
7: 'r2', // RT / R2
|
||||||
12: 'ArrowUp',
|
12: 'ArrowUp',
|
||||||
13: 'ArrowDown',
|
13: 'ArrowDown',
|
||||||
14: 'ArrowLeft',
|
14: 'ArrowLeft',
|
||||||
|
|||||||
@@ -107,6 +107,15 @@ export default function BrowseScreen({
|
|||||||
if (!hasFocus) return
|
if (!hasFocus) return
|
||||||
|
|
||||||
const onKey = (e: KeyboardEvent): void => {
|
const onKey = (e: KeyboardEvent): void => {
|
||||||
|
// Während der Wiedergabe: nur Stop erlauben, Navigation blockieren
|
||||||
|
if (isPlaying) {
|
||||||
|
e.preventDefault()
|
||||||
|
if (e.key === 'Escape' || e.key === 'Enter' || e.key === ' ') {
|
||||||
|
void window.t4sd.playback.stop()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (e.key === 'y' || e.key === 'Y') {
|
if (e.key === 'y' || e.key === 'Y') {
|
||||||
void load()
|
void load()
|
||||||
return
|
return
|
||||||
@@ -145,12 +154,10 @@ export default function BrowseScreen({
|
|||||||
if (ch) onSelectChannel(ch)
|
if (ch) onSelectChannel(ch)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'Escape': {
|
case 'Escape':
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (isPlaying) void window.t4sd.playback.stop()
|
onRequestSidebar()
|
||||||
else onRequestSidebar()
|
|
||||||
break
|
break
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// grid region
|
// grid region
|
||||||
@@ -184,12 +191,10 @@ export default function BrowseScreen({
|
|||||||
if (game) onSelectCategory(game)
|
if (game) onSelectCategory(game)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'Escape': {
|
case 'Escape':
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (isPlaying) void window.t4sd.playback.stop()
|
onRequestSidebar()
|
||||||
else onRequestSidebar()
|
|
||||||
break
|
break
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export default function CategoryScreen({ game, onSelectChannel, onBack }: Props)
|
|||||||
const [streams, setStreams] = useState<FollowedChannelInfo[]>([])
|
const [streams, setStreams] = useState<FollowedChannelInfo[]>([])
|
||||||
const [loadState, setLoadState] = useState<LoadState>('loading')
|
const [loadState, setLoadState] = useState<LoadState>('loading')
|
||||||
const [focusedIndex, setFocusedIndex] = useState(0)
|
const [focusedIndex, setFocusedIndex] = useState(0)
|
||||||
|
const [isPlaying, setIsPlaying] = useState(false)
|
||||||
const gridRef = useRef<HTMLDivElement>(null)
|
const gridRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
const load = useCallback(async () => {
|
const load = useCallback(async () => {
|
||||||
@@ -33,6 +34,13 @@ export default function CategoryScreen({ game, onSelectChannel, onBack }: Props)
|
|||||||
void load()
|
void load()
|
||||||
}, [load])
|
}, [load])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return window.t4sd.playback.onEvent((ev) => {
|
||||||
|
if (ev.kind === 'started') setIsPlaying(true)
|
||||||
|
else if (ev.kind === 'stopped' || ev.kind === 'error') setIsPlaying(false)
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
const getColumns = useCallback((): number => {
|
const getColumns = useCallback((): number => {
|
||||||
const grid = gridRef.current
|
const grid = gridRef.current
|
||||||
if (!grid) return 4
|
if (!grid) return 4
|
||||||
@@ -42,6 +50,15 @@ export default function CategoryScreen({ game, onSelectChannel, onBack }: Props)
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onKey = (e: KeyboardEvent): void => {
|
const onKey = (e: KeyboardEvent): void => {
|
||||||
|
// Während der Wiedergabe: nur Stop erlauben, Navigation blockieren
|
||||||
|
if (isPlaying) {
|
||||||
|
e.preventDefault()
|
||||||
|
if (e.key === 'Escape' || e.key === 'Enter' || e.key === ' ') {
|
||||||
|
void window.t4sd.playback.stop()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (e.key === 'y' || e.key === 'Y') {
|
if (e.key === 'y' || e.key === 'Y') {
|
||||||
void load()
|
void load()
|
||||||
return
|
return
|
||||||
@@ -94,7 +111,7 @@ export default function CategoryScreen({ game, onSelectChannel, onBack }: Props)
|
|||||||
|
|
||||||
window.addEventListener('keydown', onKey)
|
window.addEventListener('keydown', onKey)
|
||||||
return () => window.removeEventListener('keydown', onKey)
|
return () => window.removeEventListener('keydown', onKey)
|
||||||
}, [loadState, streams, focusedIndex, getColumns, onBack, onSelectChannel, load])
|
}, [loadState, streams, focusedIndex, isPlaying, getColumns, onBack, onSelectChannel, load])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="category-screen">
|
<div className="category-screen">
|
||||||
|
|||||||
@@ -73,6 +73,39 @@ export default function ChannelScreen({ channel, onBack }: Props): JSX.Element {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onKey = (e: KeyboardEvent): void => {
|
const onKey = (e: KeyboardEvent): void => {
|
||||||
|
const isPlaying = playState === 'playing' || playState === 'starting'
|
||||||
|
|
||||||
|
// Während der Wiedergabe: nur Playback-Steuerung erlauben, keine Navigation
|
||||||
|
if (isPlaying) {
|
||||||
|
switch (e.key) {
|
||||||
|
case 'l2':
|
||||||
|
e.preventDefault()
|
||||||
|
void window.t4sd.playback.seek(-300)
|
||||||
|
break
|
||||||
|
case 'r2':
|
||||||
|
e.preventDefault()
|
||||||
|
void window.t4sd.playback.seek(300)
|
||||||
|
break
|
||||||
|
case 'ArrowLeft':
|
||||||
|
e.preventDefault()
|
||||||
|
void window.t4sd.playback.seek(-30)
|
||||||
|
break
|
||||||
|
case 'ArrowRight':
|
||||||
|
e.preventDefault()
|
||||||
|
void window.t4sd.playback.seek(30)
|
||||||
|
break
|
||||||
|
case 'Enter':
|
||||||
|
case ' ':
|
||||||
|
case 'Escape':
|
||||||
|
e.preventDefault()
|
||||||
|
void window.t4sd.playback.stop()
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
e.preventDefault() // alle anderen Tasten blockieren
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (focusRegion === 'hero') {
|
if (focusRegion === 'hero') {
|
||||||
if (e.key === 'ArrowDown' && vods.length > 0) {
|
if (e.key === 'ArrowDown' && vods.length > 0) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@@ -80,18 +113,12 @@ export default function ChannelScreen({ channel, onBack }: Props): JSX.Element {
|
|||||||
setShelfIndex(0)
|
setShelfIndex(0)
|
||||||
} else if (e.key === 'Enter' || e.key === ' ') {
|
} else if (e.key === 'Enter' || e.key === ' ') {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (playState === 'playing' || playState === 'starting') {
|
if (channel.isLive) {
|
||||||
void window.t4sd.playback.stop()
|
|
||||||
} else if (channel.isLive) {
|
|
||||||
void handleWatch()
|
void handleWatch()
|
||||||
}
|
}
|
||||||
} else if (e.key === 'Escape') {
|
} else if (e.key === 'Escape') {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (playState === 'playing' || playState === 'starting') {
|
onBack()
|
||||||
void window.t4sd.playback.stop()
|
|
||||||
} else {
|
|
||||||
onBack()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// shelf region
|
// shelf region
|
||||||
@@ -111,11 +138,7 @@ export default function ChannelScreen({ channel, onBack }: Props): JSX.Element {
|
|||||||
if (vod) void handleWatchVod(vod)
|
if (vod) void handleWatchVod(vod)
|
||||||
} else if (e.key === 'Escape') {
|
} else if (e.key === 'Escape') {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (playState === 'playing' || playState === 'starting') {
|
onBack()
|
||||||
void window.t4sd.playback.stop()
|
|
||||||
} else {
|
|
||||||
onBack()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -252,7 +275,7 @@ export default function ChannelScreen({ channel, onBack }: Props): JSX.Element {
|
|||||||
|
|
||||||
{playState === 'playing' && (
|
{playState === 'playing' && (
|
||||||
<>
|
<>
|
||||||
<span className="channel-screen__playing-hint">● Wiedergabe läuft in mpv</span>
|
<span className="channel-screen__playing-hint">● Wiedergabe läuft in mpv · ← −30s · → +30s · LT −5min · RT +5min</span>
|
||||||
<button className="btn" onClick={() => void handleStop()}>
|
<button className="btn" onClick={() => void handleStop()}>
|
||||||
■ Stop (B / Escape)
|
■ Stop (B / Escape)
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
Vendored
+1
@@ -81,6 +81,7 @@ export interface T4sdApi {
|
|||||||
startLive: (channelLogin: string, quality?: string) => Promise<void>
|
startLive: (channelLogin: string, quality?: string) => Promise<void>
|
||||||
startVod: (vodId: string, channelLogin: string, title: string, durationSeconds: number) => Promise<void>
|
startVod: (vodId: string, channelLogin: string, title: string, durationSeconds: number) => Promise<void>
|
||||||
stop: () => Promise<void>
|
stop: () => Promise<void>
|
||||||
|
seek: (seconds: number) => Promise<void>
|
||||||
onEvent: (cb: (event: PlaybackEvent) => void) => () => void
|
onEvent: (cb: (event: PlaybackEvent) => void) => () => void
|
||||||
}
|
}
|
||||||
gamepad: {
|
gamepad: {
|
||||||
|
|||||||
Reference in New Issue
Block a user