Add media and channel management routes: introduce new routes for admin channels, media, and shows in the routing structure. Update navigation in the admin layout to include links for these new sections. Enhance type definitions for media assets and shows in the API types. Integrate HLS.js for improved streaming support.

This commit is contained in:
Leonid Pershin
2026-07-24 16:18:54 +03:00
parent 1bbfd15907
commit a2685fb602
24 changed files with 2050 additions and 42 deletions
+117
View File
@@ -36,3 +36,120 @@ export type PagedList<T> = {
page: number
pageSize: number
}
export type CreatedIdResponse = { id: string }
// ── Медиа ────────────────────────────────────────────────────────────────
export type MediaAssetStatus = 'Pending' | 'Processing' | 'Ready' | 'Failed'
export type MediaSource = 'Upload' | 'Inbox'
export type MediaAssetDto = {
id: string
originalFileName: string
source: MediaSource
status: MediaAssetStatus
durationSeconds: number | null
segmentCount: number | null
width: number | null
height: number | null
videoCodec: string | null
audioCodec: string | null
errorMessage: string | null
createdAt: string
}
// ── Библиотека (шоу) ───────────────────────────────────────────────────────
export type ShowKind = 'Series' | 'Single'
export type ShowSummaryDto = {
id: string
name: string
kind: ShowKind
episodeCount: number
createdAt: string
}
export type EpisodeDto = {
id: string
mediaAssetId: string
position: number
assetName: string | null
assetStatus: MediaAssetStatus | null
durationSeconds: number | null
}
export type ShowDto = {
id: string
name: string
kind: ShowKind
description: string | null
episodes: EpisodeDto[]
}
// ── Каналы ────────────────────────────────────────────────────────────────
export type BlockMode = 'Count' | 'Duration'
export type AdInsertion = 'BetweenBlocks' | 'BetweenEpisodes'
export type OverrideMode = 'Exclusive' | 'Boost'
export type ScheduleEntryKind = 'Program' | 'Ad'
export type ChannelSummaryDto = {
id: string
name: string
slug: string
isEnabled: boolean
}
export type ChannelShowDto = {
id: string
showId: string
showName: string
weight: number
blockMode: BlockMode
blockValue: number
isEnabled: boolean
nextEpisodeIndex: number
}
export type ChannelAdDto = {
id: string
mediaAssetId: string
assetName: string | null
position: number
}
export type OverrideShowDto = { showId: string; showName: string; weight: number }
export type ProgrammingOverrideDto = {
id: string
mode: OverrideMode
startsAtUtc: string
endsAtUtc: string
shows: OverrideShowDto[]
}
export type ChannelDto = {
id: string
name: string
slug: string
isEnabled: boolean
adInsertion: AdInsertion
adsPerBreak: number
fillerAssetId: string | null
shows: ChannelShowDto[]
ads: ChannelAdDto[]
overrides: ProgrammingOverrideDto[]
}
export type ScheduleEntryDto = {
id: string
kind: ScheduleEntryKind
mediaAssetId: string
startsAtUtc: string
endsAtUtc: string
showId: string | null
showName: string | null
episodeIndex: number | null
}
// ── Публичный эфир ─────────────────────────────────────────────────────────
export type PublicChannelDto = { id: string; slug: string; name: string }
+150
View File
@@ -57,6 +57,15 @@ const resources = {
placeholder: 'Список каналов появится здесь позже — пока в эфире только тестовая заставка.',
role: 'Роль',
},
air: {
now: 'Сейчас',
next: 'Далее',
ad: 'Реклама',
episode: 'Серия',
noChannels: 'Пока нет доступных каналов. Загляните позже.',
playbackError: 'Не удалось воспроизвести поток',
unsupported: 'Браузер не поддерживает воспроизведение HLS',
},
settings: {
title: 'Настройки аккаунта',
changeUserName: 'Смена имени пользователя',
@@ -91,6 +100,72 @@ const resources = {
unblock: 'Разблокировать',
filterAll: 'Все роли',
},
media: {
title: 'Медиа',
upload: 'Загрузить',
uploaded: 'Файл загружен, идёт обработка',
name: 'Файл',
status: 'Статус',
duration: 'Длительность',
resolution: 'Разрешение',
empty: 'Пока нет загруженных файлов',
statuses: {
Pending: 'В очереди',
Processing: 'Обработка',
Ready: 'Готов',
Failed: 'Ошибка',
},
},
shows: {
title: 'Шоу',
name: 'Название',
kind: 'Тип',
kinds: { Series: 'Сериал', Single: 'Полнометражка' },
episodes: 'Серии',
episode: 'Серия',
addEpisode: 'Добавить серию',
pickAsset: 'Выберите файл',
noEpisodes: 'Серий пока нет',
},
channels: {
title: 'Каналы',
name: 'Название',
slug: 'Slug',
state: 'Состояние',
enabled: 'В эфире',
disabled: 'Выключен',
enabledLabel: 'Канал в эфире',
regenerate: 'Пересобрать',
regenerated: 'Расписание пересобрано',
settings: 'Настройки',
adPolicy: 'Реклама',
betweenBlocks: 'Между блоками',
betweenEpisodes: 'Между сериями',
adsPerBreak: 'Роликов подряд',
filler: 'Заглушка',
noFiller: 'Без заглушки',
shows: 'Шоу канала',
show: 'Шоу',
weight: 'Вес',
block: 'Блок',
on: 'Вкл',
noShows: 'Шоу не добавлены',
pickShow: 'Выберите шоу',
blockCount: 'По сериям',
blockDuration: 'По времени',
episodes: 'серий',
minutes: 'минут',
ads: 'Реклама',
pickAd: 'Выберите ролик',
noAds: 'Пул рекламы пуст',
overrides: 'Марафоны / override',
modes: { Exclusive: 'Эксклюзив', Boost: 'Буст' },
from: 'С',
to: 'По',
noOverrides: 'Override не заданы',
schedule: 'Расписание (12 ч)',
noSchedule: 'Расписание ещё не построено — нажмите «Пересобрать»',
},
},
},
},
@@ -149,6 +224,15 @@ const resources = {
placeholder: 'The channel list will show up here later — for now, enjoy the test card.',
role: 'Role',
},
air: {
now: 'Now',
next: 'Up next',
ad: 'Ad',
episode: 'Episode',
noChannels: 'No channels available yet. Check back later.',
playbackError: 'Failed to play the stream',
unsupported: 'This browser cannot play HLS',
},
settings: {
title: 'Account settings',
changeUserName: 'Change username',
@@ -183,6 +267,72 @@ const resources = {
unblock: 'Unblock',
filterAll: 'All roles',
},
media: {
title: 'Media',
upload: 'Upload',
uploaded: 'File uploaded, processing started',
name: 'File',
status: 'Status',
duration: 'Duration',
resolution: 'Resolution',
empty: 'No uploaded files yet',
statuses: {
Pending: 'Queued',
Processing: 'Processing',
Ready: 'Ready',
Failed: 'Failed',
},
},
shows: {
title: 'Shows',
name: 'Name',
kind: 'Kind',
kinds: { Series: 'Series', Single: 'Movie' },
episodes: 'Episodes',
episode: 'Episode',
addEpisode: 'Add episode',
pickAsset: 'Pick a file',
noEpisodes: 'No episodes yet',
},
channels: {
title: 'Channels',
name: 'Name',
slug: 'Slug',
state: 'State',
enabled: 'On air',
disabled: 'Off',
enabledLabel: 'Channel on air',
regenerate: 'Rebuild',
regenerated: 'Schedule rebuilt',
settings: 'Settings',
adPolicy: 'Ads',
betweenBlocks: 'Between blocks',
betweenEpisodes: 'Between episodes',
adsPerBreak: 'Ads per break',
filler: 'Filler',
noFiller: 'No filler',
shows: 'Channel shows',
show: 'Show',
weight: 'Weight',
block: 'Block',
on: 'On',
noShows: 'No shows added',
pickShow: 'Pick a show',
blockCount: 'By episodes',
blockDuration: 'By time',
episodes: 'episodes',
minutes: 'minutes',
ads: 'Ads',
pickAd: 'Pick an ad',
noAds: 'Ad pool is empty',
overrides: 'Marathons / overrides',
modes: { Exclusive: 'Exclusive', Boost: 'Boost' },
from: 'From',
to: 'To',
noOverrides: 'No overrides set',
schedule: 'Schedule (12h)',
noSchedule: 'Schedule not built yet — click “Rebuild”',
},
},
},
},