watch channel change on player page, ffplayout#351

This commit is contained in:
jb-alvarado 2023-07-14 13:45:48 +02:00
parent 6d356e0cd7
commit eef6310a8d
9 changed files with 141 additions and 87 deletions

View File

@ -3,7 +3,7 @@
<div class="container-fluid">
<div class="row control-row">
<div class="col-3 player-col d-flex flex-column">
<div class="d-flex flex-grow-1 align-items-center">
<div>
<video v-if="streamExtension === 'flv'" ref="httpStreamFlv" class="w-100" controls />
<VideoPlayer
class="live-player"
@ -68,6 +68,7 @@
<div>
<button
title="Start Playout Service"
data-tooltip=tooltip
class="btn btn-primary control-button control-button-play"
:class="isPlaying"
@click="controlProcess('start')"
@ -80,6 +81,7 @@
<div>
<button
title="Stop Playout Service"
data-tooltip=tooltip
class="btn btn-primary control-button control-button-stop"
@click="controlProcess('stop')"
>
@ -91,6 +93,7 @@
<div>
<button
title="Restart Playout Service"
data-tooltip=tooltip
class="btn btn-primary control-button control-button-restart"
@click="controlProcess('restart')"
>
@ -103,6 +106,7 @@
<div>
<button
title="Jump to last Clip"
data-tooltip=tooltip
class="btn btn-primary control-button control-button-control"
@click="controlPlayout('back')"
>
@ -114,6 +118,7 @@
<div>
<button
title="Reset Playout State"
data-tooltip=tooltip
class="btn btn-primary control-button control-button-control"
@click="controlPlayout('reset')"
>
@ -125,6 +130,7 @@
<div>
<button
title="Jump to next Clip"
data-tooltip=tooltip
class="btn btn-primary control-button control-button-control"
@click="controlPlayout('next')"
>
@ -142,6 +148,7 @@
</template>
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import mpegts from 'mpegts.js'
import { useAuth } from '~/stores/auth'
import { useConfig } from '~/stores/config'
@ -152,6 +159,7 @@ const authStore = useAuth()
const configStore = useConfig()
const playlistStore = usePlaylist()
const { filename, secToHMS, timeToSeconds } = stringFormatter()
const { configID } = storeToRefs(useConfig())
const contentType = { 'content-type': 'application/json;charset=UTF-8' }
const isPlaying = ref('')
@ -190,6 +198,22 @@ onMounted(() => {
status()
})
watch([configID], () => {
breakStatusCheck.value = false
timeStr.value = '00:00:00'
playlistStore.remainingSec = -1
playlistStore.currentClip = ''
playlistStore.ingestRuns = false
playlistStore.currentClipDuration = 0
playlistStore.currentClipIn = 0
playlistStore.currentClipOut = 0
if (timer.value) {
clearTimeout(timer.value)
}
status()
})
onBeforeUnmount(() => {
breakStatusCheck.value = true

View File

@ -112,8 +112,8 @@ async function addChannel() {
newChannel.service = `ffplayout@${confName}.service`
channels.push(newChannel)
configStore.updateGuiConfig(channels)
configStore.updateConfigID(configStore.configGui.length - 1)
configStore.configGui =channels
configStore.configID = configStore.configGui.length - 1
}
async function onSubmitGui() {
@ -154,8 +154,8 @@ async function deleteChannel() {
})
config.splice(configStore.configID, 1)
configStore.updateGuiConfig(config)
configStore.updateConfigID(configStore.configGui.length - 1)
configStore.configGui = config
configStore.configID = configStore.configGui.length - 1
await configStore.getPlayoutConfig()
if (response.status === 200) {

View File

@ -83,7 +83,7 @@ function logout() {
}
function selectChannel(index: number) {
configStore.updateConfigID(index)
configStore.configID = index
configStore.getPlayoutConfig()
}
</script>

View File

@ -17,6 +17,7 @@
import { useConfig } from '~/stores/config'
import { useIndex } from '~/stores/index'
const { $bootstrap } = useNuxtApp()
const configStore = useConfig()
const indexStore = useIndex()
@ -27,6 +28,13 @@ useHead({
}
})
onMounted(() => {
// @ts-ignore
new $bootstrap.Tooltip(document.body, {
selector: "[data-tooltip=tooltip]",
container: "body"
})
})
await configStore.nuxtClientInit()
</script>

View File

@ -273,6 +273,7 @@
type="button"
class="btn btn-primary"
title="Create Folder"
data-tooltip=tooltip
data-bs-toggle="modal"
data-bs-target="#folderModal"
>
@ -282,6 +283,7 @@
type="button"
class="btn btn-primary"
title="Upload File"
data-tooltip=tooltip
data-bs-toggle="modal"
data-bs-target="#uploadModal"
>
@ -438,15 +440,26 @@ const thisUploadModal = ref()
const xhr = ref(new XMLHttpRequest())
onMounted(async () => {
let config_extensions = configStore.configPlayout.storage.extensions
let extra_extensions = configStore.configGui[configStore.configID].extra_extensions
if (typeof config_extensions === 'string') {
config_extensions = config_extensions.split(',')
}
if (typeof extra_extensions === 'string') {
extra_extensions = extra_extensions.split(',')
}
const exts = [
...configStore.configPlayout.storage.extensions.split(','),
...configStore.configGui[configStore.configID].extra_extensions.split(','),
...config_extensions,
...extra_extensions,
].map((ext) => {
return `.${ext}`
})
extensions.value = exts.join(', ')
// @ts-ignore
// @ts-ignore
thisUploadModal.value = $bootstrap.Modal.getOrCreateInstance(uploadModal.value)
if (!mediaStore.folderTree.parent) {

View File

@ -11,12 +11,13 @@
</div>
<div class="col-2">
<div class="btn-group" role="group">
<button class="btn btn-primary" title="Save Preset" @click="savePreset()">
<button class="btn btn-primary" title="Save Preset" data-tooltip=tooltip @click="savePreset()">
<i class="bi-cloud-upload" />
</button>
<button
class="btn btn-primary"
title="New Preset"
data-tooltip=tooltip
data-bs-toggle="modal"
data-bs-target="#createModal"
>
@ -25,6 +26,7 @@
<button
class="btn btn-primary"
title="Delete Preset"
data-tooltip=tooltip
data-bs-toggle="modal"
data-bs-target="#deleteModal"
>
@ -45,6 +47,7 @@
v-model="form.x"
type="text"
title="X Axis"
data-tooltip=tooltip
placeholder="X"
required
/>
@ -53,6 +56,7 @@
v-model="form.y"
type="text"
title="Y Axis"
data-tooltip=tooltip
placeholder="Y"
required
/>

View File

@ -200,13 +200,20 @@
</splitpanes>
<div class="btn-group media-button mb-3">
<div class="btn btn-primary" title="Copy Playlist" data-bs-toggle="modal" data-bs-target="#copyModal">
<div
class="btn btn-primary"
title="Copy Playlist"
data-bs-toggle="modal"
data-tooltip="tooltip"
data-bs-target="#copyModal"
>
<i class="bi-files" />
</div>
<div
v-if="!configStore.configPlayout.playlist.loop"
class="btn btn-primary"
title="Loop Clips in Playlist"
data-tooltip="tooltip"
@click="loopClips()"
>
<i class="bi-view-stacked" />
@ -214,6 +221,7 @@
<div
class="btn btn-primary"
title="Add (remote) Source to Playlist"
data-tooltip="tooltip"
data-bs-toggle="modal"
data-bs-target="#sourceModal"
@click="clearNewSource()"
@ -223,6 +231,7 @@
<div
class="btn btn-primary"
title="Import text/m3u file"
data-tooltip="tooltip"
data-bs-toggle="modal"
data-bs-target="#importModal"
>
@ -231,19 +240,26 @@
<div
class="btn btn-primary"
title="Generate a randomized Playlist"
data-tooltip="tooltip"
data-bs-toggle="modal"
data-bs-target="#generateModal"
@click="mediaStore.getTree('', true)"
>
<i class="bi-sort-down-alt" />
</div>
<div class="btn btn-primary" title="Reset Playlist" @click="getPlaylist()">
<div class="btn btn-primary" title="Reset Playlist" data-tooltip="tooltip" @click="getPlaylist()">
<i class="bi-arrow-counterclockwise" />
</div>
<div class="btn btn-primary" title="Save Playlist" @click="savePlaylist(listDate)">
<div class="btn btn-primary" title="Save Playlist" data-tooltip="tooltip" @click="savePlaylist(listDate)">
<i class="bi-download" />
</div>
<div class="btn btn-primary" title="Delete Playlist" data-bs-toggle="modal" data-bs-target="#deleteModal">
<div
class="btn btn-primary"
title="Delete Playlist"
data-tooltip="tooltip"
data-bs-toggle="modal"
data-bs-target="#deleteModal"
>
<i class="bi-trash" />
</div>
</div>
@ -483,7 +499,7 @@
'/'
),
true
)
),
]
"
>
@ -550,11 +566,11 @@ const mediaStore = useMedia()
const playlistStore = usePlaylist()
definePageMeta({
middleware: ['auth']
middleware: ['auth'],
})
useHead({
title: 'Player | ffplayout'
title: 'Player | ffplayout',
})
const { configID } = storeToRefs(useConfig())
@ -575,12 +591,12 @@ const selectedFolders = ref([] as string[])
const generateFromAll = ref(false)
const browserSortOptions = ref({
group: { name: 'playlist', pull: 'clone', put: false },
sort: false
sort: false,
})
const playlistSortOptions = ref({
group: 'playlist',
animation: 100,
handle: '.grabbing'
handle: '.grabbing',
})
const newSource = ref({
begin: 0,
@ -591,7 +607,7 @@ const newSource = ref({
custom_filter: '',
source: '',
audio: '',
uid: ''
uid: '',
} as PlaylistItem)
onMounted(() => {
@ -674,7 +690,7 @@ function cloneClip(event: any) {
source: sourcePath,
in: 0,
out: mediaStore.folderTree.files[o].duration,
duration: mediaStore.folderTree.files[o].duration
duration: mediaStore.folderTree.files[o].duration,
})
playlistStore.playlist = processPlaylist(
@ -719,9 +735,9 @@ function setPreviewData(path: string) {
sources: [
{
type: `video/${ext}`,
src: previewUrl.value
}
]
src: previewUrl.value,
},
],
}
} else {
isVideo.value = false
@ -760,7 +776,7 @@ function processSource(evt: any) {
custom_filter: '',
source: '',
audio: '',
uid: ''
uid: '',
}
}
@ -775,7 +791,7 @@ function clearNewSource() {
custom_filter: '',
source: '',
audio: '',
uid: genUID()
uid: genUID(),
}
}
@ -791,7 +807,7 @@ function editPlaylistItem(i: number) {
custom_filter: playlistStore.playlist[i].custom_filter,
source: playlistStore.playlist[i].source,
audio: playlistStore.playlist[i].audio,
uid: playlistStore.playlist[i].uid
uid: playlistStore.playlist[i].uid,
}
}
@ -811,7 +827,7 @@ function loopClips() {
const tempList = []
let length = 0
while (length < configStore.playlistLength) {
while (length < configStore.playlistLength && playlistStore.playlist.length > 0) {
for (const item of playlistStore.playlist) {
if (length < configStore.playlistLength) {
tempList.push($_.cloneDeep(item))
@ -843,7 +859,7 @@ async function onSubmitImport(evt: any) {
{
method: 'PUT',
headers: authStore.authHeader,
body: formData
body: formData,
}
)
.then(() => {
@ -878,7 +894,7 @@ async function generatePlaylist() {
await $fetch(`/api/playlist/${configStore.configGui[configStore.configID].id}/generate/${listDate.value}`, {
method: 'POST',
headers: { ...contentType, ...authStore.authHeader },
body: (selectedFolders.value.length > 0 && !generateFromAll.value) ? { paths: selectedFolders.value } : null
body: selectedFolders.value.length > 0 && !generateFromAll.value ? { paths: selectedFolders.value } : null,
})
.then((response: any) => {
playlistStore.playlist = processPlaylist(
@ -909,6 +925,10 @@ async function generatePlaylist() {
}
async function savePlaylist(saveDate: string) {
if (playlistStore.playlist.length === 0) {
return
}
playlistStore.playlist = processPlaylist(
configStore.startInSec,
configStore.playlistLength,
@ -923,8 +943,8 @@ async function savePlaylist(saveDate: string) {
body: JSON.stringify({
channel: configStore.configGui[configStore.configID].name,
date: saveDate,
program: saveList
})
program: saveList,
}),
})
.then((response: any) => {
indexStore.alertVariant = 'alert-success'
@ -959,7 +979,7 @@ async function savePlaylist(saveDate: string) {
async function deletePlaylist(playlistDate: string) {
await $fetch(`/api/playlist/${configStore.configGui[configStore.configID].id}/${playlistDate}`, {
method: 'DELETE',
headers: { ...contentType, ...authStore.authHeader }
headers: { ...contentType, ...authStore.authHeader },
}).then(() => {
playlistStore.playlist = []

View File

@ -9,7 +9,7 @@ import { useIndex } from '~/stores/index'
interface GuiConfig {
id: number
config_path: string
extra_extensions: string
extra_extensions: string | string[]
name: string
preview_url: string
service: string
@ -38,42 +38,6 @@ export const useConfig = defineStore('config', {
getters: {},
actions: {
updateConfigID(id: number) {
this.configID = id
},
updateConfigCount(count: number) {
this.configCount = count
},
updateGuiConfig(config: GuiConfig[]) {
this.configGui = config
},
updateGuiConfigRaw(config: GuiConfig[]) {
this.configGuiRaw = config
},
updateStartTime(sec: number) {
this.startInSec = sec
},
updatePlaylistLength(sec: number) {
this.playlistLength = sec
},
setCurrentUser(user: string) {
this.currentUser = user
},
updateUserConfig(config: User) {
this.configUser = config
},
updateUtcOffset(offset: number) {
this.utcOffset = offset
},
async nuxtClientInit() {
const authStore = useAuth()
@ -102,10 +66,10 @@ export const useConfig = defineStore('config', {
})
.then((response) => response.json())
.then((objs) => {
this.updateUtcOffset(objs[0].utc_offset)
this.updateGuiConfig(objs)
this.updateGuiConfigRaw(_.cloneDeep(objs))
this.updateConfigCount(objs.length)
this.utcOffset = objs[0].utc_offset
this.configGui = objs
this.configGuiRaw = _.cloneDeep(objs)
this.configCount = objs.length
})
.catch((e) => {
if (statusCode === 401) {
@ -116,7 +80,7 @@ export const useConfig = defineStore('config', {
navigateTo('/')
}
this.updateGuiConfig([
this.configGui = [
{
id: 1,
config_path: '',
@ -126,7 +90,7 @@ export const useConfig = defineStore('config', {
service: '',
uts_offset: 0,
},
])
]
indexStore.alertMsg = e
indexStore.alertVariant = 'alert-danger'
@ -164,9 +128,9 @@ export const useConfig = defineStore('config', {
}
}
this.updateGuiConfig(guiConfigs)
this.updateGuiConfigRaw(_.cloneDeep(guiConfigs))
this.updateConfigCount(guiConfigs.length)
this.configGui = guiConfigs
this.configGuiRaw = _.cloneDeep(guiConfigs)
this.configCount = guiConfigs.length
await this.getPlayoutConfig()
}
@ -186,13 +150,11 @@ export const useConfig = defineStore('config', {
.then((response) => response.json())
.then((data) => {
if (data.playlist.day_start) {
const start = timeToSeconds(data.playlist.day_start)
this.updateStartTime(start)
this.startInSec = timeToSeconds(data.playlist.day_start)
}
if (data.playlist.length) {
const length = timeToSeconds(data.playlist.length)
this.updatePlaylistLength(length)
this.playlistLength = timeToSeconds(data.playlist.length)
}
if (data.storage.extensions) {
@ -213,7 +175,12 @@ export const useConfig = defineStore('config', {
const channel = this.configGui[this.configID].id
const contentType = { 'content-type': 'application/json;charset=UTF-8' }
obj.storage.extensions = obj.storage.extensions.replace(' ', '').split(/,|;/)
this.startInSec = timeToSeconds(obj.playlist.day_start)
this.playlistLength = timeToSeconds(obj.playlist.length)
if (typeof obj.storage.extensions === 'string') {
obj.storage.extensions = obj.storage.extensions.replace(' ', '').split(/,|;/)
}
const update = await fetch(`/api/playout/config/${channel}`, {
method: 'PUT',
@ -233,8 +200,8 @@ export const useConfig = defineStore('config', {
})
.then((response) => response.json())
.then((data) => {
this.setCurrentUser(data.username)
this.updateUserConfig(data)
this.currentUser = data.username
this.configUser = data
})
},

View File

@ -2,6 +2,7 @@ import { defineStore } from 'pinia'
import { useAuth } from '~/stores/auth'
import { useConfig } from '~/stores/config'
import { useIndex } from '~/stores/index'
export const useMedia = defineStore('media', {
state: () => ({
@ -17,6 +18,7 @@ export const useMedia = defineStore('media', {
async getTree(path: string, foldersOnly: boolean = false) {
const authStore = useAuth()
const configStore = useConfig()
const indexStore = useIndex()
const contentType = { 'content-type': 'application/json;charset=UTF-8' }
const channel = configStore.configGui[configStore.configID].id
const crumbs: Crumb[] = []
@ -27,7 +29,23 @@ export const useMedia = defineStore('media', {
headers: { ...contentType, ...authStore.authHeader },
body: JSON.stringify({ source: path, folders_only: foldersOnly }),
})
.then((response) => response.json())
.then((response) => {
if (response.status === 200) {
return response.json()
} else {
indexStore.alertVariant = 'alert-danger'
indexStore.alertMsg = `Storage not exist!`
indexStore.showAlert = true
return {
source: '',
parent: '',
folders: [],
files: [],
}
}
})
.then((data) => {
const pathStr = 'Home/' + data.source
const pathArr = pathStr.split('/')