get playout status from sse
This commit is contained in:
parent
3763af9d40
commit
c62607023b
@ -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()
|
||||||
|
@ -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) {
|
|
||||||
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
|
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>
|
||||||
|
@ -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()
|
|
||||||
const configStore = useConfig()
|
|
||||||
const channel = configStore.configGui[configStore.configID].id
|
|
||||||
|
|
||||||
await fetch(`/api/control/${channel}/media/current`, {
|
|
||||||
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.playoutIsRunning = true
|
||||||
this.currentClip = data.current_media.source
|
this.currentClip = item.media.source
|
||||||
this.currentClipIndex = data.index
|
this.currentClipIn = item.media.in
|
||||||
this.currentClipStart = data.start_sec
|
this.currentClipOut = item.media.out
|
||||||
this.currentClipDuration = data.current_media.duration
|
this.currentClipDuration = item.media.duration
|
||||||
this.currentClipIn = data.current_media.seek
|
this.currentClipIndex = item.index
|
||||||
this.currentClipOut = data.current_media.out
|
this.playedSec = item.played
|
||||||
this.remainingSec = data.remaining_sec
|
this.ingestRuns = item.ingest
|
||||||
this.ingestRuns = data.ingest_runs
|
|
||||||
}
|
this.progressValue = (this.playedSec * 100) / this.currentClipOut- this.currentClipIn
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
this.playoutIsRunning = false
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
8
types/index.d.ts
vendored
8
types/index.d.ts
vendored
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user