mirror of
https://github.com/Andre-Bzk/twitch4steamdeck.git
synced 2026-11-15 09:33:09 +01:00
A single-language interface makes the app less usable for part of the audience and encourages more hardcoded copy as new screens and states are added. Introducing a shared i18n path with a persisted language preference gives the UI one consistent way to present text and lowers the risk of partial or inconsistent localization over time. The surrounding copy cleanup belongs in the same change because it aligns existing labels, messages, and settings text with that translation structure instead of leaving mixed localization approaches in place.
90 lines
2.5 KiB
TypeScript
90 lines
2.5 KiB
TypeScript
import { EventEmitter } from 'node:events'
|
|
import { getAvailableQualities, getStreamUrl } from './streamlink'
|
|
import type { PlaybackEvent } from './types'
|
|
import * as history from '../store/historyRepo'
|
|
|
|
export interface HlsUrlPayload {
|
|
url: string
|
|
isLive: boolean
|
|
startPosition: number
|
|
durationSeconds: number
|
|
vodId?: string
|
|
}
|
|
|
|
export class PlaybackService extends EventEmitter {
|
|
async startLive(channelLogin: string, quality = 'best'): Promise<void> {
|
|
let hlsUrl: string
|
|
try {
|
|
hlsUrl = await getStreamUrl(`twitch.tv/${channelLogin}`, quality)
|
|
} catch (e) {
|
|
this.emit('playback-event', {
|
|
kind: 'error',
|
|
message: `Failed to resolve stream URL: ${e}`
|
|
} satisfies PlaybackEvent)
|
|
return
|
|
}
|
|
this.emit('playback-hls-url', {
|
|
url: hlsUrl,
|
|
isLive: true,
|
|
startPosition: 0,
|
|
durationSeconds: 0
|
|
} satisfies HlsUrlPayload)
|
|
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,
|
|
quality = 'best'
|
|
): Promise<void> {
|
|
const resumePos = history.getPosition(vodId)
|
|
history.upsertVod({
|
|
vod_id: vodId,
|
|
channel_login: channelLogin,
|
|
title,
|
|
duration_seconds: durationSeconds,
|
|
watched_at: Date.now()
|
|
})
|
|
|
|
let hlsUrl: string
|
|
try {
|
|
hlsUrl = await getStreamUrl(`https://www.twitch.tv/videos/${vodId}`, quality)
|
|
} catch (e) {
|
|
this.emit('playback-event', {
|
|
kind: 'error',
|
|
message: `Failed to resolve stream URL: ${e}`
|
|
} satisfies PlaybackEvent)
|
|
return
|
|
}
|
|
|
|
const effectiveStart = startSeconds !== undefined ? startSeconds : resumePos
|
|
this.emit('playback-hls-url', {
|
|
url: hlsUrl,
|
|
isLive: false,
|
|
startPosition: effectiveStart,
|
|
durationSeconds,
|
|
vodId
|
|
} satisfies HlsUrlPayload)
|
|
this.emit('playback-event', { kind: 'started', durationSeconds, isLive: false } satisfies PlaybackEvent)
|
|
}
|
|
|
|
stop(): void {
|
|
this.emit('playback-event', { kind: 'stopped' } satisfies PlaybackEvent)
|
|
}
|
|
|
|
updateVodPosition(vodId: string, positionSeconds: number, durationSeconds: number): void {
|
|
const pos = Math.floor(positionSeconds)
|
|
history.updatePosition(vodId, pos)
|
|
if (durationSeconds > 0 && pos / durationSeconds > 0.95) {
|
|
history.markCompleted(vodId)
|
|
}
|
|
}
|
|
}
|