diff --git a/components/player/Player.vue b/components/player/Player.vue index 04aed2fa..5b17a16b 100644 --- a/components/player/Player.vue +++ b/components/player/Player.vue @@ -134,7 +134,7 @@
{{ song.nextMedia.title }}
{{ song.nextMedia.artists }}
-{{ $t('panel.next') }}
{{ song.nextMedia.title }}
-{{ $t('player.schedule_loading') }}
++ {{ $t(scheduleIsReference ? 'panel.schedule_reference_note' : 'panel.schedule_official_note') }} +
+ + {{ $t('panel.schedule_official_link') }} + + +{{ $t('panel.schedule_vietnam_timezone') }}
++ {{ $t('panel.schedule_provider_only') }} +
@@ -266,6 +280,8 @@ const props = withDefaults( stationName: string; countryName?: string; scheduleLoading?: boolean; + scheduleSourceUrl?: string; + scheduleIsReference?: boolean; }>(), { playlist: () => [], @@ -275,6 +291,8 @@ const props = withDefaults( activeStationUrl: "", countryName: "", scheduleLoading: false, + scheduleSourceUrl: "", + scheduleIsReference: false, }, ); @@ -295,7 +313,7 @@ const isActiveStation = (station: RadioStationOption): boolean => ? station.url === props.activeStationUrl : station.id === props.activeStationId; const hasSchedule = computed( - () => props.playlist.length > 0 || props.program.length > 0, + () => props.playlist.length > 0 || props.program.length > 0 || Boolean(props.scheduleSourceUrl), ); const scheduleCount = computed( () => props.program.length || props.playlist.length, @@ -359,6 +377,19 @@ watch([hasStations, hasSchedule], ([stationsReady, scheduleReady]) => { } }); +const vietnamTimeFormatter = new Intl.DateTimeFormat('en-GB', { + timeZone: 'Asia/Ho_Chi_Minh', hour: '2-digit', minute: '2-digit', hourCycle: 'h23', +}); +const formatProgramTime = (timestamp: number): string => props.scheduleIsReference + ? vietnamTimeFormatter.format(new Date(timestamp * 1000)) + : dayjs.unix(timestamp).format('HH:mm'); +// Provider EPG usually puts the current programme first. The reference +// guide must never falsely highlight its first (morning) slot as "on air". +const isCurrentProgram = (item: ProgramItem, index: number): boolean => + props.scheduleIsReference + ? Boolean(item.startTime && item.endTime && item.startTime <= Date.now() / 1000 && item.endTime > Date.now() / 1000) + : index === 0; + const playlistKey = (item: PlaylistItem, index: number): string => [item.track_title, item.track_artist, item.track_played, index].join("-"); diff --git a/components/player/Session.vue b/components/player/Session.vue index 571490c9..b348f93c 100644 --- a/components/player/Session.vue +++ b/components/player/Session.vue @@ -15,6 +15,8 @@ :playlist="playlist" :song="song" :queue-loading="loading" + :schedule-source-url="scheduleSourceUrl" + :schedule-is-reference="scheduleIsReference" :lyrics-available="isZingStation" @select-station="selectStation" @open-lyrics="lyricsOpen = true" @@ -34,7 +36,7 @@ import type { RadioSource, RadioStationOption } from '~/types/radio'; const playback = usePlaybackStore(); useRestoreLastStation(); const { station } = storeToRefs(playback); -const { program, playlist, song, lyrics, lyricsOpen, loading, isZingStation } = useStationRealtime(); +const { program, playlist, song, lyrics, lyricsOpen, loading, isZingStation, scheduleSourceUrl, scheduleIsReference } = useStationRealtime(); const emptySource: RadioSource = { src: '', type: 'audio/mpeg' }; const emptySources: RadioSource[] = []; diff --git a/composables/useStationRealtime.ts b/composables/useStationRealtime.ts index 8fb08215..7c9e1d5b 100644 --- a/composables/useStationRealtime.ts +++ b/composables/useStationRealtime.ts @@ -1,6 +1,7 @@ import { usePlaybackStore } from '~/stores/playback'; import type { RadioProgram, RadioSong, RadioTrack } from '~/types/radio'; import { isZingScheduleUrl } from '~/utils/station-schedule.mjs'; +import { getVietnamSchedule, vietnamReferencePrograms } from '~/utils/vietnam-schedules.mjs'; import { createStationPolling } from '~/utils/station-polling.mjs'; import { resolveLiveTrack } from '~/utils/live-track.mjs'; @@ -17,6 +18,9 @@ export function useStationRealtime() { let clientReady = false; const isZingStation = computed(() => isZingScheduleUrl(playback.station?.schedule)); + const referenceSchedule = computed(() => getVietnamSchedule(playback.station?.url)); + const scheduleSourceUrl = computed(() => referenceSchedule.value?.sourceUrl || ''); + const scheduleIsReference = computed(() => Boolean(referenceSchedule.value)); const timestampUrl = (url: string) => /[?&](?:time|timestamp)=$/i.test(url) ? `${url}${Math.floor(Date.now() / 1000)}` : url; const fetchLyric = async (id: string, signal: AbortSignal): Promise+ {{ $t('panel.schedule_vietnam_timezone') }} +
++ {{ $t(officialPrograms.length ? 'panel.schedule_reference_note' : 'panel.schedule_provider_only') }} +
+