import { APICreate, APIListPaginated, APIRemove, APIRetrieve, APIUpdate, createExtendableAPI, } from '@rokoli/bnb/drf' import { defineStore } from 'pinia' import { createTankURL } from '@/api' import { tankAuthInit } from '@/stores/auth' import { Playlist } from '@/types' import { URLBuilder } from '@rokoli/bnb' import { useShowStore } from '@/stores/shows' import { computed, MaybeRefOrGetter, toValue } from 'vue' export function calculatePlaylistDurationInSeconds(playlist: Playlist, skipUnknown?: true): number export function calculatePlaylistDurationInSeconds( playlist: Playlist, skipUnknown?: false, ): number | null /** * Calculates the duration of a playlist. * May return null if the playlist contains entries with an invalid duration. * @param playlist * @param skipUnknown Whether unknown length entries should be skipped */ export function calculatePlaylistDurationInSeconds(playlist: Playlist, skipUnknown = false) { let duration = 0 for (const entry of playlist.entries) { // entry.duration may be null/NaN if the entry references // a stream or other resources without an inherent duration if (typeof entry.duration !== 'number' || isNaN(entry.duration)) { if (skipUnknown) continue else return null } duration += entry.duration } return duration } export function countUnknownDurations(entries: Playlist['entries']) { let counter = 0 for (const entry of entries) { if (typeof entry.duration !== 'number' || isNaN(entry.duration)) counter += 1 } return counter } export function usePlaylistState( playlist: MaybeRefOrGetter<Playlist | null>, targetDurationSeconds: MaybeRefOrGetter<number>, ) { return computed(() => { const _playlist = toValue(playlist) if (!_playlist) return { state: 'missing' as const } const _targetDuration = Math.round(toValue(targetDurationSeconds)) const unknownDurationCount = countUnknownDurations(_playlist.entries) let playlistDuration = Math.round(calculatePlaylistDurationInSeconds(_playlist, true)) // If the playlist contains just one record of unknown length // the playout will automatically expand that entry to the remaining // time that is needed to fill the timeslot. // We can therefore consider a single entry of unknown duration to fit the required time. if (unknownDurationCount === 1 && playlistDuration < _targetDuration) { playlistDuration = _targetDuration } if (unknownDurationCount > 1) return { state: 'indeterminate' as const, duration: playlistDuration, offset: Math.abs(_targetDuration - playlistDuration), } if (playlistDuration < _targetDuration) return { state: 'tooShort' as const, duration: playlistDuration, offset: _targetDuration - playlistDuration, } if (playlistDuration > _targetDuration) return { state: 'tooLong' as const, duration: playlistDuration, offset: playlistDuration - _targetDuration, } return { state: 'ok' as const } }) } const playlistsShowEndpoint: URLBuilder = (...subPaths) => { const showStore = useShowStore() return createTankURL.prefix( 'shows', showStore.selectedShow?.slug as string, 'playlists', )(...subPaths) } export const usePlaylistStore = defineStore('playlists', () => { const endpoint = createTankURL.prefix('playlists') const { api, base } = createExtendableAPI<Playlist>(endpoint, tankAuthInit) const { list } = APIListPaginated(api) const { update } = APIUpdate({ ...api, endpoint: playlistsShowEndpoint }) return { ...base, list, update, ...APIRetrieve(api), ...APICreate({ ...api, endpoint: playlistsShowEndpoint }), ...APIRemove({ ...api, endpoint: playlistsShowEndpoint }), } })