get playout status from sse

This commit is contained in:
jb-alvarado 2024-04-29 09:35:26 +02:00
parent 3763af9d40
commit c62607023b
4 changed files with 114 additions and 100 deletions

View File

@ -3,7 +3,7 @@
<NuxtLink class="navbar-brand min-w-[46px] p-2" href="/"> <NuxtLink class="navbar-brand min-w-[46px] p-2" href="/">
<img src="~/assets/images/ffplayout-small.png" class="img-fluid" alt="Logo" width="30" height="30" /> <img src="~/assets/images/ffplayout-small.png" class="img-fluid" alt="Logo" width="30" height="30" />
</NuxtLink> </NuxtLink>
<EventStatus class="z-10"/> <EventStatus v-if="route.name?.toString().includes('player__')" class="z-10"/>
<div class="navbar-end w-1/5 grow"> <div class="navbar-end w-1/5 grow">
<label class="swap swap-rotate me-2 md:hidden"> <label class="swap swap-rotate me-2 md:hidden">
<input type="checkbox" :checked="indexStore.darkMode" @change="toggleDarkTheme" /> <input type="checkbox" :checked="indexStore.darkMode" @change="toggleDarkTheme" />
@ -96,6 +96,7 @@
const colorMode = useColorMode() const colorMode = useColorMode()
const { t } = useI18n() const { t } = useI18n()
const localePath = useLocalePath() const localePath = useLocalePath()
const route = useRoute()
const router = useRouter() const router = useRouter()
const authStore = useAuth() const authStore = useAuth()

View File

@ -43,7 +43,7 @@
<div <div
class="w-full h-full bg-base-100 rounded font-['DigitalNumbers'] p-6 text-3xl md:text-2xl 2xl:text-5xl 4xl:text-7xl tracking-tighter flex justify-center items-center shadow" class="w-full h-full bg-base-100 rounded font-['DigitalNumbers'] p-6 text-3xl md:text-2xl 2xl:text-5xl 4xl:text-7xl tracking-tighter flex justify-center items-center shadow"
> >
{{ secToHMS(playlistStore.remainingSec >= 0 ? playlistStore.remainingSec : 0) }} {{ secToHMS(timeRemaining()) }}
</div> </div>
</div> </div>
@ -58,7 +58,7 @@
class="h-1/3 font-bold text truncate" class="h-1/3 font-bold text truncate"
:title="filename(playlistStore.currentClip)" :title="filename(playlistStore.currentClip)"
> >
{{ filename(playlistStore.currentClip) }} {{ filename(playlistStore.currentClip) || $t('control.noClip') }}
</div> </div>
<div class="grow"> <div class="grow">
<strong>{{ $t('player.duration') }}:</strong> <strong>{{ $t('player.duration') }}:</strong>
@ -161,14 +161,15 @@ import mpegts from 'mpegts.js'
const { $dayjs } = useNuxtApp() const { $dayjs } = useNuxtApp()
const authStore = useAuth() const authStore = useAuth()
const configStore = useConfig() const configStore = useConfig()
const indexStore = useIndex()
const playlistStore = usePlaylist() const playlistStore = usePlaylist()
const { filename, secToHMS, timeToSeconds } = stringFormatter() const { filename, secToHMS } = stringFormatter()
const { configID } = storeToRefs(useConfig()) const { configID } = storeToRefs(useConfig())
playlistStore.currentClip = 'Es wird kein Clip abgespielt' playlistStore.currentClip = 'Es wird kein Clip abgespielt'
const breakStatusCheck = ref(false)
const timeStr = ref('00:00:00') const timeStr = ref('00:00:00')
const timer = ref() const timer = ref()
const errorCounter = ref(0)
const streamExtension = ref(configStore.configGui[configStore.configID].preview_url.split('.').pop()) const streamExtension = ref(configStore.configGui[configStore.configID].preview_url.split('.').pop())
const httpStreamFlv = ref(null) const httpStreamFlv = ref(null)
const httpFlvSource = ref({ const httpFlvSource = ref({
@ -181,8 +182,22 @@ const mpegtsOptions = ref({
liveBufferLatencyChasing: true, liveBufferLatencyChasing: true,
}) })
const streamUrl = ref(
`/data/event/${configStore.configGui[configStore.configID].id}?endpoint=playout&uuid=${authStore.uuid}`
)
// 'http://127.0.0.1:8787/data/event/1?endpoint=system&uuid=f2f8c29b-712a-48c5-8919-b535d3a05a3a'
const { status, data, error, close } = useEventSource(streamUrl, [], {
autoReconnect: {
retries: -1,
delay: 1000,
onFailed() {
indexStore.sseConnected = false
},
},
})
onMounted(() => { onMounted(() => {
breakStatusCheck.value = false
let player: any = null let player: any = null
if (streamExtension.value === 'flv' && mpegts.getFeatureList().mseLivePlayback) { if (streamExtension.value === 'flv' && mpegts.getFeatureList().mseLivePlayback) {
@ -198,68 +213,87 @@ onMounted(() => {
player.load() player.load()
} }
status() clock()
})
onBeforeUnmount(() => {
indexStore.sseConnected = false
close()
if (timer.value) {
clearTimeout(timer.value)
}
})
watch([status, error], async () => {
if (status.value === 'OPEN') {
indexStore.sseConnected = true
errorCounter.value = 0
} else {
indexStore.sseConnected = false
errorCounter.value += 1
if (errorCounter.value > 15) {
await authStore.obtainUuid()
streamUrl.value = `/data/event/${configStore.configGui[configStore.configID].id}?endpoint=playout&uuid=${
authStore.uuid
}`
errorCounter.value = 0
}
}
})
watch([data], () => {
if (data.value) {
try {
const playout_status = JSON.parse(data.value)
playlistStore.setStatus(playout_status)
} catch (_) {
indexStore.sseConnected = true
resetStatus()
}
}
}) })
watch([configID], () => { watch([configID], () => {
breakStatusCheck.value = false resetStatus()
timeStr.value = '00:00:00'
playlistStore.remainingSec = -1 streamUrl.value = `/data/event/${configStore.configGui[configStore.configID].id}?endpoint=playout&uuid=${
authStore.uuid
}`
if (timer.value) {
clearTimeout(timer.value)
}
})
function timeRemaining() {
let remaining = playlistStore.currentClipOut - playlistStore.currentClipIn - playlistStore.playedSec
if (remaining < 0) {
remaining = 0
}
return remaining
}
async function clock() {
async function setTime(resolve: any) {
timeStr.value = $dayjs().utcOffset(configStore.utcOffset).format('HH:mm:ss')
timer.value = setTimeout(() => setTime(resolve), 1000)
}
return new Promise((resolve) => setTime(resolve))
}
function resetStatus() {
playlistStore.playedSec = 0
playlistStore.currentClip = '' playlistStore.currentClip = ''
playlistStore.ingestRuns = false playlistStore.ingestRuns = false
playlistStore.currentClipDuration = 0 playlistStore.currentClipDuration = 0
playlistStore.currentClipIn = 0 playlistStore.currentClipIn = 0
playlistStore.currentClipOut = 0 playlistStore.currentClipOut = 0
playlistStore.playoutIsRunning = false
if (timer.value) { playlistStore.progressValue = 0
clearTimeout(timer.value)
}
status()
})
onBeforeUnmount(() => {
breakStatusCheck.value = true
if (timer.value) {
clearTimeout(timer.value)
}
})
async function status() {
/*
Get playout state and information's from current clip.
- animate timers
- when clip end is reached call API again and set new values
*/
await playlistStore.playoutStat()
async function setStatus(resolve: any) {
/*
recursive function as a endless loop
*/
timeStr.value = $dayjs().utcOffset(configStore.utcOffset).format('HH:mm:ss')
const timeInSec = timeToSeconds(timeStr.value)
playlistStore.remainingSec = playlistStore.currentClipStart + playlistStore.currentClipOut - timeInSec
const playedSec = playlistStore.currentClipOut - playlistStore.remainingSec
if (playlistStore.currentClipOut === 0 || !playlistStore.playoutIsRunning) {
playlistStore.progressValue = 0
} else {
playlistStore.progressValue = (playedSec * 100) / playlistStore.currentClipOut
}
if (breakStatusCheck.value) {
return
} else if ((playlistStore.playoutIsRunning && playlistStore.remainingSec < 0) || timeInSec % 30 === 0) {
// When 30 seconds a passed, get new status.
await playlistStore.playoutStat()
} else if (!playlistStore.playoutIsRunning) {
playlistStore.remainingSec = 0
}
timer.value = setTimeout(() => setStatus(resolve), 1000)
}
return new Promise((resolve) => setStatus(resolve))
} }
async function controlProcess(state: string) { async function controlProcess(state: string) {
@ -273,10 +307,6 @@ async function controlProcess(state: string) {
headers: { ...configStore.contentType, ...authStore.authHeader }, headers: { ...configStore.contentType, ...authStore.authHeader },
body: JSON.stringify({ command: state }), body: JSON.stringify({ command: state }),
}) })
setTimeout(async () => {
await playlistStore.playoutStat()
}, 1000)
} }
async function controlPlayout(state: string) { async function controlPlayout(state: string) {
@ -293,9 +323,5 @@ async function controlPlayout(state: string) {
headers: { ...configStore.contentType, ...authStore.authHeader }, headers: { ...configStore.contentType, ...authStore.authHeader },
body: JSON.stringify({ control: state }), body: JSON.stringify({ control: state }),
}) })
setTimeout(async () => {
await playlistStore.playoutStat()
}, 1000)
} }
</script> </script>

View File

@ -22,7 +22,7 @@ export const usePlaylist = defineStore('playlist', {
currentClipIn: 0, currentClipIn: 0,
currentClipOut: 0, currentClipOut: 0,
ingestRuns: false, ingestRuns: false,
remainingSec: 0, playedSec: 0,
playoutIsRunning: false, playoutIsRunning: false,
}), }),
@ -74,38 +74,17 @@ export const usePlaylist = defineStore('playlist', {
}) })
}, },
async playoutStat() { setStatus(item: PlayoutStatus) {
const authStore = useAuth() this.playoutIsRunning = true
const configStore = useConfig() this.currentClip = item.media.source
const channel = configStore.configGui[configStore.configID].id this.currentClipIn = item.media.in
this.currentClipOut = item.media.out
this.currentClipDuration = item.media.duration
this.currentClipIndex = item.index
this.playedSec = item.played
this.ingestRuns = item.ingest
await fetch(`/api/control/${channel}/media/current`, { this.progressValue = (this.playedSec * 100) / this.currentClipOut- this.currentClipIn
method: 'GET',
headers: authStore.authHeader,
})
.then((response) => {
if (response.status === 503) {
this.playoutIsRunning = false
}
return response.json()
})
.then((data) => {
if (data && data.played_sec) {
this.playoutIsRunning = true
this.currentClip = data.current_media.source
this.currentClipIndex = data.index
this.currentClipStart = data.start_sec
this.currentClipDuration = data.current_media.duration
this.currentClipIn = data.current_media.seek
this.currentClipOut = data.current_media.out
this.remainingSec = data.remaining_sec
this.ingestRuns = data.ingest_runs
}
})
.catch(() => {
this.playoutIsRunning = false
})
}, },
}, },
}) })

8
types/index.d.ts vendored
View File

@ -119,4 +119,12 @@ declare global {
swap: { total: number; used: number; free: number } swap: { total: number; used: number; free: number }
system: { name?: string; kernel?: string; version?: string; ffp_version?: string } system: { name?: string; kernel?: string; version?: string; ffp_version?: string }
} }
interface PlayoutStatus {
media: PlaylistItem
index: number
ingest: boolean
mode: string
played: number
}
} }