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) {
|
||||
if (value < -STICK_THRESHOLD) key = 'ArrowUp'
|
||||
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 {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ export const IPC = {
|
||||
playbackStartLive: 'playback:start-live',
|
||||
playbackStartVod: 'playback:start-vod',
|
||||
playbackStop: 'playback:stop',
|
||||
playbackSeek: 'playback:seek',
|
||||
/** main → renderer */
|
||||
playbackEvent: 'playback:event'
|
||||
} as const
|
||||
@@ -74,6 +75,7 @@ export function registerIpcHandlers(
|
||||
playback.startVod(vodId, channelLogin, title, durationSeconds)
|
||||
)
|
||||
ipcMain.handle(IPC.playbackStop, () => playback.stop())
|
||||
ipcMain.handle(IPC.playbackSeek, (_e, seconds: number) => playback.seek(seconds))
|
||||
|
||||
playback.on('playback-event', (event: PlaybackEvent) => {
|
||||
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 {
|
||||
if (this.socket?.writable) {
|
||||
try {
|
||||
|
||||
@@ -141,6 +141,10 @@ export class PlaybackService extends EventEmitter {
|
||||
})
|
||||
}
|
||||
|
||||
seek(seconds: number): void {
|
||||
this.current?.mpv.seek(seconds)
|
||||
}
|
||||
|
||||
async stop(): Promise<void> {
|
||||
if (!this.current) return
|
||||
const { process: proc, mpv } = this.current
|
||||
|
||||
@@ -17,6 +17,7 @@ const IPC = {
|
||||
playbackStartLive: 'playback:start-live',
|
||||
playbackStartVod: 'playback:start-vod',
|
||||
playbackStop: 'playback:stop',
|
||||
playbackSeek: 'playback:seek',
|
||||
playbackEvent: 'playback:event',
|
||||
|
||||
gamepadInput: 'gamepad-input'
|
||||
@@ -114,6 +115,7 @@ const api = {
|
||||
startVod: (vodId: string, channelLogin: string, title: string, durationSeconds: number): Promise<void> =>
|
||||
ipcRenderer.invoke(IPC.playbackStartVod, vodId, channelLogin, title, durationSeconds),
|
||||
stop: (): Promise<void> => ipcRenderer.invoke(IPC.playbackStop),
|
||||
seek: (seconds: number): Promise<void> => ipcRenderer.invoke(IPC.playbackSeek, seconds),
|
||||
onEvent: (cb: (event: PlaybackEvent) => void): (() => void) => {
|
||||
const listener = (_e: IpcRendererEvent, event: PlaybackEvent): void => cb(event)
|
||||
ipcRenderer.on(IPC.playbackEvent, listener)
|
||||
|
||||
@@ -23,6 +23,8 @@ const BUTTON_MAP: Record<number, string> = {
|
||||
1: 'Escape', // B
|
||||
2: 'x', // X
|
||||
3: 'y', // Y
|
||||
6: 'l2', // LT / L2
|
||||
7: 'r2', // RT / R2
|
||||
12: 'ArrowUp',
|
||||
13: 'ArrowDown',
|
||||
14: 'ArrowLeft',
|
||||
|
||||
@@ -107,6 +107,15 @@ export default function BrowseScreen({
|
||||
if (!hasFocus) return
|
||||
|
||||
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') {
|
||||
void load()
|
||||
return
|
||||
@@ -145,13 +154,11 @@ export default function BrowseScreen({
|
||||
if (ch) onSelectChannel(ch)
|
||||
break
|
||||
}
|
||||
case 'Escape': {
|
||||
case 'Escape':
|
||||
e.preventDefault()
|
||||
if (isPlaying) void window.t4sd.playback.stop()
|
||||
else onRequestSidebar()
|
||||
onRequestSidebar()
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// grid region
|
||||
const cols = getGridColumns()
|
||||
@@ -184,15 +191,13 @@ export default function BrowseScreen({
|
||||
if (game) onSelectCategory(game)
|
||||
break
|
||||
}
|
||||
case 'Escape': {
|
||||
case 'Escape':
|
||||
e.preventDefault()
|
||||
if (isPlaying) void window.t4sd.playback.stop()
|
||||
else onRequestSidebar()
|
||||
onRequestSidebar()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('keydown', onKey)
|
||||
return () => window.removeEventListener('keydown', onKey)
|
||||
|
||||
@@ -14,6 +14,7 @@ export default function CategoryScreen({ game, onSelectChannel, onBack }: Props)
|
||||
const [streams, setStreams] = useState<FollowedChannelInfo[]>([])
|
||||
const [loadState, setLoadState] = useState<LoadState>('loading')
|
||||
const [focusedIndex, setFocusedIndex] = useState(0)
|
||||
const [isPlaying, setIsPlaying] = useState(false)
|
||||
const gridRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const load = useCallback(async () => {
|
||||
@@ -33,6 +34,13 @@ export default function CategoryScreen({ game, onSelectChannel, onBack }: Props)
|
||||
void 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 grid = gridRef.current
|
||||
if (!grid) return 4
|
||||
@@ -42,6 +50,15 @@ export default function CategoryScreen({ game, onSelectChannel, onBack }: Props)
|
||||
|
||||
useEffect(() => {
|
||||
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') {
|
||||
void load()
|
||||
return
|
||||
@@ -94,7 +111,7 @@ export default function CategoryScreen({ game, onSelectChannel, onBack }: Props)
|
||||
|
||||
window.addEventListener('keydown', onKey)
|
||||
return () => window.removeEventListener('keydown', onKey)
|
||||
}, [loadState, streams, focusedIndex, getColumns, onBack, onSelectChannel, load])
|
||||
}, [loadState, streams, focusedIndex, isPlaying, getColumns, onBack, onSelectChannel, load])
|
||||
|
||||
return (
|
||||
<div className="category-screen">
|
||||
|
||||
@@ -73,6 +73,39 @@ export default function ChannelScreen({ channel, onBack }: Props): JSX.Element {
|
||||
|
||||
useEffect(() => {
|
||||
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 (e.key === 'ArrowDown' && vods.length > 0) {
|
||||
e.preventDefault()
|
||||
@@ -80,19 +113,13 @@ export default function ChannelScreen({ channel, onBack }: Props): JSX.Element {
|
||||
setShelfIndex(0)
|
||||
} else if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault()
|
||||
if (playState === 'playing' || playState === 'starting') {
|
||||
void window.t4sd.playback.stop()
|
||||
} else if (channel.isLive) {
|
||||
if (channel.isLive) {
|
||||
void handleWatch()
|
||||
}
|
||||
} else if (e.key === 'Escape') {
|
||||
e.preventDefault()
|
||||
if (playState === 'playing' || playState === 'starting') {
|
||||
void window.t4sd.playback.stop()
|
||||
} else {
|
||||
onBack()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// shelf region
|
||||
if (e.key === 'ArrowLeft') {
|
||||
@@ -111,14 +138,10 @@ export default function ChannelScreen({ channel, onBack }: Props): JSX.Element {
|
||||
if (vod) void handleWatchVod(vod)
|
||||
} else if (e.key === 'Escape') {
|
||||
e.preventDefault()
|
||||
if (playState === 'playing' || playState === 'starting') {
|
||||
void window.t4sd.playback.stop()
|
||||
} else {
|
||||
onBack()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
window.addEventListener('keydown', onKey)
|
||||
return () => window.removeEventListener('keydown', onKey)
|
||||
}, [focusRegion, playState, vods, shelfIndex, onBack])
|
||||
@@ -252,7 +275,7 @@ export default function ChannelScreen({ channel, onBack }: Props): JSX.Element {
|
||||
|
||||
{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()}>
|
||||
■ Stop (B / Escape)
|
||||
</button>
|
||||
|
||||
Vendored
+1
@@ -81,6 +81,7 @@ export interface T4sdApi {
|
||||
startLive: (channelLogin: string, quality?: string) => Promise<void>
|
||||
startVod: (vodId: string, channelLogin: string, title: string, durationSeconds: number) => Promise<void>
|
||||
stop: () => Promise<void>
|
||||
seek: (seconds: number) => Promise<void>
|
||||
onEvent: (cb: (event: PlaybackEvent) => void) => () => void
|
||||
}
|
||||
gamepad: {
|
||||
|
||||
Reference in New Issue
Block a user