Add IPTV playlist functionality and enhance streaming endpoints
Implemented a new endpoint for downloading the IPTV playlist, allowing users to access a comprehensive M3U file containing all channels and program guides. Updated the StreamingEndpoints to support token validation for both cookie and query parameters, ensuring secure access for external IPTV players. Enhanced the AirPage component to include a download button for the IPTV playlist, with appropriate user feedback on success or failure. Updated localization strings to reflect new features and instructions for users.
This commit is contained in:
@@ -1,14 +1,22 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Radio, RotateCw } from 'lucide-react'
|
||||
import { Download, Radio, RotateCw } from 'lucide-react'
|
||||
import { qk } from '@/shared/api/query-keys'
|
||||
import type { PublicEpgEntryDto } from '@/shared/api/types'
|
||||
import { cn } from '@/shared/lib/cn'
|
||||
import { Badge } from '@/shared/ui/badge'
|
||||
import { Button } from '@/shared/ui/button'
|
||||
import { toast } from '@/shared/ui/toast-store'
|
||||
import { ChannelPlayer } from './ChannelPlayer'
|
||||
import { getEpg, getViewerFeatures, imageUrl, listChannels, watchChannel } from './api'
|
||||
import {
|
||||
downloadIptvPlaylist,
|
||||
getEpg,
|
||||
getViewerFeatures,
|
||||
imageUrl,
|
||||
listChannels,
|
||||
watchChannel,
|
||||
} from './api'
|
||||
|
||||
function formatTime(iso: string) {
|
||||
return new Date(iso).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
|
||||
@@ -51,6 +59,21 @@ export function AirPage() {
|
||||
setAttempt((a) => a + 1)
|
||||
}
|
||||
|
||||
const [downloading, setDownloading] = useState(false)
|
||||
const downloadPlaylist = async () => {
|
||||
setDownloading(true)
|
||||
try {
|
||||
await downloadIptvPlaylist()
|
||||
// Предупреждение показываем в момент скачивания, а не подписью под кнопкой: висеть на экране
|
||||
// ему незачем, а прочитать его надо ровно тогда, когда файл уже на руках.
|
||||
toast.success(t('air.iptvDone'))
|
||||
} catch {
|
||||
toast.error(t('air.iptvFailed'))
|
||||
} finally {
|
||||
setDownloading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const { data: channels, isLoading } = useQuery({
|
||||
queryKey: qk.air.channels,
|
||||
queryFn: listChannels,
|
||||
@@ -166,9 +189,21 @@ export function AirPage() {
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-wrap items-center justify-between gap-2">
|
||||
<h1 className="crt-glow text-2xl font-bold">{t('nav.dashboard')}</h1>
|
||||
{numbersEnabled && (
|
||||
<span className="text-xs text-muted-foreground">{t('air.numbersHint')}</span>
|
||||
)}
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
{numbersEnabled && (
|
||||
<span className="text-xs text-muted-foreground">{t('air.numbersHint')}</span>
|
||||
)}
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
disabled={downloading}
|
||||
title={t('air.iptvHint')}
|
||||
onClick={downloadPlaylist}
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
{t('air.iptv')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-[220px_1fr]">
|
||||
|
||||
Reference in New Issue
Block a user