feat: add quality selection, improve chapter panel UX, and show VOD view counts

Quality selection during playback:
- Add getAvailableQualities() in streamlink via --json, sorted by fixed order
- Expose playback:get-qualities IPC channel (handlers, preload, type declarations)
- Add quality panel in PlaybackOverlay:
  - Button + dropdown
  - Keyboard navigation (X: open, arrows: navigate, Enter: confirm, Escape: close)
- Quality change triggers streamlink restart; VOD resumes at current position
- Available in ChannelScreen (Live + VOD) and AppShell global overlay (Live)
- Add QualityIcon

Chapter panel UX improvements:
- Video no longer pauses when chapter panel opens
- PlaybackOverlay is hidden via conditional render instead
- Remove wasPlayingBeforeChapters state (no longer needed)
- X button seeks to video start when no chapters are found, with hint in panel UI

VOD cards and defaults:
- Show view count with eye icon on VOD cards (matching playback overlay style)
- Change default streamBadgeMode from 'language' to 'both'
This commit is contained in:
Andre
2026-05-10 12:13:34 +02:00
parent 23eeb9912c
commit 1251cc278a
14 changed files with 594 additions and 81 deletions
+8 -3
View File
@@ -1,5 +1,5 @@
import { EventEmitter } from 'node:events'
import { getStreamUrl } from './streamlink'
import { getAvailableQualities, getStreamUrl } from './streamlink'
import type { PlaybackEvent } from './types'
import * as history from '../store/historyRepo'
@@ -32,12 +32,17 @@ export class PlaybackService extends EventEmitter {
this.emit('playback-event', { kind: 'started', isLive: true } satisfies PlaybackEvent)
}
getAvailableQualities(twitchUrl: string): Promise<string[]> {
return getAvailableQualities(twitchUrl)
}
async startVod(
vodId: string,
channelLogin: string,
title: string,
durationSeconds: number,
startSeconds?: number
startSeconds?: number,
quality = 'best'
): Promise<void> {
const resumePos = history.getPosition(vodId)
history.upsertVod({
@@ -50,7 +55,7 @@ export class PlaybackService extends EventEmitter {
let hlsUrl: string
try {
hlsUrl = await getStreamUrl(`https://www.twitch.tv/videos/${vodId}`, 'best')
hlsUrl = await getStreamUrl(`https://www.twitch.tv/videos/${vodId}`, quality)
} catch (e) {
this.emit('playback-event', {
kind: 'error',