v5.0.0 - support new API
17
README.md
@ -15,25 +15,22 @@ After installations you have to setup ssl for your **https** connections.
|
||||
|
||||
## Some Impressions:
|
||||
#### Login
|
||||
![login](/docs/assets/login.png)
|
||||
|
||||
#### Landing Page
|
||||
![landing-page](/docs/assets/landing-page.png)
|
||||
![login](/docs/images/login.png)
|
||||
|
||||
#### Control Page
|
||||
![control](/docs/assets/control.png)
|
||||
![control](/docs/images/control.png)
|
||||
|
||||
#### Media Page
|
||||
![media](/docs/assets/media.png)
|
||||
![media](/docs/images/media.png)
|
||||
|
||||
#### Media Page / Upload
|
||||
![media-upload](/docs/assets/media-upload.png)
|
||||
![media-upload](/docs/images/media-upload.png)
|
||||
|
||||
#### Message Page
|
||||
![message](/docs/assets/message.png)
|
||||
![message](/docs/images/message.png)
|
||||
|
||||
#### Logging Page
|
||||
![logging](/docs/assets/logging.png)
|
||||
![logging](/docs/images/logging.png)
|
||||
|
||||
#### Configuration Page / GUI
|
||||
![config-gui](/docs/assets/config-gui.png)
|
||||
![config-gui](/docs/images/config-gui.png)
|
||||
|
Before Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 160 KiB |
Before Width: | Height: | Size: 77 KiB |
Before Width: | Height: | Size: 378 KiB |
Before Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 63 KiB |
Before Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 61 KiB |
BIN
docs/images/config-gui.png
Normal file
After Width: | Height: | Size: 64 KiB |
BIN
docs/images/control.png
Normal file
After Width: | Height: | Size: 326 KiB |
BIN
docs/images/logging.png
Normal file
After Width: | Height: | Size: 122 KiB |
BIN
docs/images/login.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
docs/images/media-upload.png
Normal file
After Width: | Height: | Size: 187 KiB |
BIN
docs/images/media.png
Normal file
After Width: | Height: | Size: 209 KiB |
BIN
docs/images/message.png
Normal file
After Width: | Height: | Size: 60 KiB |
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "ffplayout",
|
||||
"name": "ffplayout-frontend",
|
||||
"version": "5.0.0",
|
||||
"description": "web GUI for ffplayout engine",
|
||||
"description": "Web GUI for ffplayout",
|
||||
"author": "Jonathan Baecker",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
@ -21,7 +21,7 @@
|
||||
v-for="(crumb, index) in crumbs"
|
||||
:key="crumb.key"
|
||||
:active="index === crumbs.length - 1"
|
||||
@click="getPath(extensions, crumb.path)"
|
||||
@click="getPath(crumb.path)"
|
||||
>
|
||||
{{ crumb.text }}
|
||||
</b-breadcrumb-item>
|
||||
@ -43,7 +43,7 @@
|
||||
<b-icon-folder-fill class="browser-icons" />
|
||||
</b-col>
|
||||
<b-col class="browser-item-text">
|
||||
<b-link @click="getPath(extensions, `/${folderTree.source}/${folder}`)">
|
||||
<b-link @click="getPath(`/${folderTree.source}/${folder}`)">
|
||||
{{ folder }}
|
||||
</b-link>
|
||||
</b-col>
|
||||
@ -304,7 +304,7 @@ export default {
|
||||
|
||||
watch: {
|
||||
configID () {
|
||||
this.getPath(this.extensions, '')
|
||||
this.getPath('')
|
||||
}
|
||||
},
|
||||
|
||||
@ -314,14 +314,14 @@ export default {
|
||||
})
|
||||
|
||||
this.extensions = exts.join(',')
|
||||
this.getPath(this.extensions, '')
|
||||
this.getPath('')
|
||||
},
|
||||
|
||||
methods: {
|
||||
async getPath (extensions, path) {
|
||||
async getPath (path) {
|
||||
this.lastPath = path
|
||||
this.isLoading = true
|
||||
await this.$store.dispatch('media/getTree', { extensions, path })
|
||||
await this.$store.dispatch('media/getTree', { path })
|
||||
this.isLoading = false
|
||||
},
|
||||
|
||||
@ -369,7 +369,7 @@ export default {
|
||||
)
|
||||
|
||||
this.$root.$emit('bv::hide::modal', 'folder-modal')
|
||||
this.getPath(this.extensions, this.lastPath)
|
||||
this.getPath(this.lastPath)
|
||||
},
|
||||
|
||||
onCancelCreateFolder (evt) {
|
||||
@ -429,7 +429,7 @@ export default {
|
||||
this.currentNumber = 0
|
||||
this.inputPlaceholder = 'Choose files or drop them here...'
|
||||
this.inputFiles = []
|
||||
this.getPath(this.extensions, this.lastPath)
|
||||
this.getPath(this.lastPath)
|
||||
this.$root.$emit('bv::hide::modal', 'upload-modal')
|
||||
},
|
||||
|
||||
@ -442,7 +442,7 @@ export default {
|
||||
this.inputPlaceholder = 'Choose files or drop them here...'
|
||||
|
||||
this.cancelTokenSource.cancel('Upload cancelled')
|
||||
this.getPath(this.extensions, this.lastPath)
|
||||
this.getPath(this.lastPath)
|
||||
|
||||
this.$root.$emit('bv::hide::modal', 'upload-modal')
|
||||
},
|
||||
@ -489,7 +489,7 @@ export default {
|
||||
}
|
||||
)
|
||||
|
||||
this.getPath(this.extensions, this.lastPath)
|
||||
this.getPath(this.lastPath)
|
||||
|
||||
this.renamePath = ''
|
||||
this.renameOldName = ''
|
||||
@ -537,7 +537,7 @@ export default {
|
||||
|
||||
this.$root.$emit('bv::hide::modal', 'delete-modal')
|
||||
|
||||
this.getPath(this.extensions, this.lastPath)
|
||||
this.getPath(this.lastPath)
|
||||
},
|
||||
|
||||
cancelDelete () {
|
||||
|
@ -266,13 +266,13 @@ export default {
|
||||
},
|
||||
|
||||
watch: {
|
||||
selected (id) {
|
||||
this.getPreset(id)
|
||||
selected (index) {
|
||||
this.getPreset(index)
|
||||
}
|
||||
},
|
||||
|
||||
created () {
|
||||
this.getPreset('')
|
||||
this.getPreset(null)
|
||||
},
|
||||
|
||||
methods: {
|
||||
@ -284,34 +284,34 @@ export default {
|
||||
return (parseFloat(parseInt(num, 16)) / 255).toFixed(2)
|
||||
},
|
||||
|
||||
async getPreset (id) {
|
||||
async getPreset (index) {
|
||||
const response = await this.$axios.get(`api/presets/${this.configGui[this.configID].id}`)
|
||||
|
||||
if (response.data && !id) {
|
||||
if (response.data && index === null) {
|
||||
this.presets = []
|
||||
for (const item of response.data) {
|
||||
this.presets.push({ value: item.id, text: item.name })
|
||||
for (let index = 0; index < response.data.length; index++) {
|
||||
const elem = response.data[index]
|
||||
this.presets.push({ value: index, text: elem.name })
|
||||
}
|
||||
} else if (response.data) {
|
||||
id -= 1
|
||||
const fColor = response.data[id].fontcolor.split('@')
|
||||
const bColor = response.data[id].boxcolor.split('@')
|
||||
const fColor = response.data[index].fontcolor.split('@')
|
||||
const bColor = response.data[index].boxcolor.split('@')
|
||||
|
||||
this.form = {
|
||||
id: response.data[id].id,
|
||||
name: response.data[id].name,
|
||||
text: response.data[id].text,
|
||||
x: response.data[id].x,
|
||||
y: response.data[id].y,
|
||||
fontSize: response.data[id].fontsize,
|
||||
fontSpacing: response.data[id].line_spacing,
|
||||
id: response.data[index].id,
|
||||
name: response.data[index].name,
|
||||
text: response.data[index].text,
|
||||
x: response.data[index].x,
|
||||
y: response.data[index].y,
|
||||
fontSize: response.data[index].fontsize,
|
||||
fontSpacing: response.data[index].line_spacing,
|
||||
fontColor: fColor[0],
|
||||
fontAlpha: (fColor[1]) ? this.hexToDec(fColor[1]) : 1.0,
|
||||
showBox: response.data[id].box,
|
||||
showBox: response.data[index].box,
|
||||
boxColor: bColor[0],
|
||||
boxAlpha: (bColor[1]) ? this.hexToDec(bColor[1]) : 1.0,
|
||||
border: response.data[id].boxborderw,
|
||||
overallAlpha: response.data[id].alpha
|
||||
border: response.data[index].boxborderw,
|
||||
overallAlpha: response.data[index].alpha
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -344,7 +344,7 @@ export default {
|
||||
|
||||
if (response.status === 200) {
|
||||
this.success = true
|
||||
this.getPreset('')
|
||||
this.getPreset(null)
|
||||
} else {
|
||||
this.failed = true
|
||||
}
|
||||
@ -394,7 +394,7 @@ export default {
|
||||
}
|
||||
|
||||
this.$bvModal.hide('delete-modal')
|
||||
this.getPreset('')
|
||||
this.getPreset(null)
|
||||
},
|
||||
|
||||
async submitMessage () {
|
||||
|
113
pages/player.vue
@ -152,7 +152,7 @@
|
||||
v-for="(crumb, index) in crumbs"
|
||||
:key="crumb.key"
|
||||
:active="index === crumbs.length - 1"
|
||||
@click="getPath(extensions, crumb.path)"
|
||||
@click="getPath(crumb.path)"
|
||||
>
|
||||
{{ crumb.text }}
|
||||
</b-breadcrumb-item>
|
||||
@ -166,7 +166,7 @@
|
||||
:key="folder.key"
|
||||
class="browser-item"
|
||||
>
|
||||
<b-link @click="getPath(extensions, `/${folderTree.source}/${folder}`)">
|
||||
<b-link @click="getPath(`/${folderTree.source}/${folder}`)">
|
||||
<b-icon-folder-fill class="browser-icons" /> {{ folder }}
|
||||
</b-link>
|
||||
</b-list-group-item>
|
||||
@ -310,6 +310,9 @@
|
||||
<b-button v-if="!configPlayout.playlist.loop" v-b-tooltip.hover title="Loop Clips in Playlist" variant="primary" @click="loopClips()">
|
||||
<b-icon-view-stacked />
|
||||
</b-button>
|
||||
<b-button v-b-tooltip.hover title="Add (remote) Source to Playlist" variant="primary" @click="showAddSource()">
|
||||
<b-icon-file-earmark-plus />
|
||||
</b-button>
|
||||
<b-button v-b-tooltip.hover title="Generate a randomized Playlist" variant="primary" @click="generatePlaylist(listDate)">
|
||||
<b-icon-sort-down-alt />
|
||||
</b-button>
|
||||
@ -351,6 +354,35 @@
|
||||
>
|
||||
Delete program from {{ listDate }}
|
||||
</b-modal>
|
||||
<b-modal
|
||||
id="add-source-modal"
|
||||
ref="add-source-modal"
|
||||
title="Add (remote) Source"
|
||||
@ok="handleSource"
|
||||
>
|
||||
<form ref="form" @submit.stop.prevent="addSource">
|
||||
<b-form-group label="In" label-for="in-input">
|
||||
<b-form-input id="in-input" v-model.number="newSource.in" type="number" inline />
|
||||
</b-form-group>
|
||||
<b-form-group label="Out" label-for="out-input" invalid-feedback="Out is required">
|
||||
<b-form-input id="out-input" v-model.number="newSource.out" type="number" inline required />
|
||||
</b-form-group>
|
||||
<b-form-group label="Duration" label-for="duration-input" invalid-feedback="Out is required">
|
||||
<b-form-input id="duration-input" v-model.number="newSource.duration" type="number" inline required />
|
||||
</b-form-group>
|
||||
<b-form-group label="Source" label-for="source-input" invalid-feedback="Source is required">
|
||||
<b-form-input id="source-input" v-model="newSource.source" required />
|
||||
</b-form-group>
|
||||
<b-form-checkbox
|
||||
id="ad-input"
|
||||
v-model="newSource.category"
|
||||
value="advertisement"
|
||||
:unchecked-value="newSource.category"
|
||||
>
|
||||
Advertisement
|
||||
</b-form-checkbox>
|
||||
</form>
|
||||
</b-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -398,7 +430,6 @@ export default {
|
||||
listDate: this.$dayjs().tz(this.timezone).format('YYYY-MM-DD'),
|
||||
targetDate: this.$dayjs().tz(this.timezone).format('YYYY-MM-DD'),
|
||||
interval: null,
|
||||
extensions: '',
|
||||
videoOptions: {
|
||||
liveui: true,
|
||||
controls: true,
|
||||
@ -424,6 +455,14 @@ export default {
|
||||
scrollOP: {
|
||||
suppressScrollX: true,
|
||||
minScrollbarLength: 30
|
||||
},
|
||||
newSource: {
|
||||
begin: 0,
|
||||
in: 0,
|
||||
out: 0,
|
||||
duration: 0,
|
||||
category: '',
|
||||
source: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -460,7 +499,7 @@ export default {
|
||||
}
|
||||
]
|
||||
|
||||
this.getPath(this.extensions, '')
|
||||
this.getPath('')
|
||||
this.getPlaylist()
|
||||
setTimeout(() => { scrollTo(this) }, 3000)
|
||||
}
|
||||
@ -475,8 +514,7 @@ export default {
|
||||
]
|
||||
|
||||
this.getStatus()
|
||||
this.extensions = this.configPlayout.storage.extensions.join(',')
|
||||
await this.getPath(this.extensions, '')
|
||||
await this.getPath('')
|
||||
|
||||
const timeInSec = this.$timeToSeconds(this.$dayjs().tz(this.timezone).format('HH:mm:ss'))
|
||||
const listStartSec = this.$timeToSeconds(this.configPlayout.playlist.day_start)
|
||||
@ -489,20 +527,15 @@ export default {
|
||||
},
|
||||
|
||||
mounted () {
|
||||
// if (process.env.NODE_ENV === 'production') {
|
||||
// this.interval = setInterval(() => {
|
||||
// this.$store.dispatch('playlist/animClock')
|
||||
// this.getStatus()
|
||||
// }, 5000)
|
||||
// } else {
|
||||
// this.$store.dispatch('playlist/animClock')
|
||||
// }
|
||||
|
||||
this.interval = setInterval(() => {
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
this.interval = setInterval(() => {
|
||||
this.$store.dispatch('playlist/playoutStat')
|
||||
this.getStatus()
|
||||
}, 5000)
|
||||
this.$store.dispatch('playlist/playoutStat')
|
||||
this.getStatus()
|
||||
}, 5000)
|
||||
this.$store.dispatch('playlist/playoutStat')
|
||||
} else {
|
||||
this.$store.dispatch('playlist/playoutStat')
|
||||
}
|
||||
|
||||
const streamExtension = this.configGui[this.configID].preview_url.split('.').pop()
|
||||
let player
|
||||
@ -533,9 +566,9 @@ export default {
|
||||
},
|
||||
|
||||
methods: {
|
||||
async getPath (extensions, path) {
|
||||
async getPath (path) {
|
||||
this.browserIsLoading = true
|
||||
await this.$store.dispatch('media/getTree', { extensions, path })
|
||||
await this.$store.dispatch('media/getTree', { path })
|
||||
this.browserIsLoading = false
|
||||
},
|
||||
|
||||
@ -591,14 +624,12 @@ export default {
|
||||
this.$root.$emit('bv::show::modal', 'preview-modal')
|
||||
},
|
||||
|
||||
cloneClip ({ file, duration }) {
|
||||
cloneClip ({ name, duration }) {
|
||||
const storagePath = this.configPlayout.storage.path
|
||||
const storagePathArr = storagePath.split('/')
|
||||
const storageRoot = storagePathArr.pop()
|
||||
const sourcePath = `${storagePathArr.join('/')}/${this.folderTree.tree[0].substring(this.folderTree.tree[0].indexOf(storageRoot))}`
|
||||
const sourcePath = `${storagePath}/${this.folderTree.source}/${name}`.replace('//', '/')
|
||||
|
||||
return {
|
||||
source: `${sourcePath}/${file}`,
|
||||
source: sourcePath,
|
||||
in: 0,
|
||||
out: duration,
|
||||
duration
|
||||
@ -709,6 +740,36 @@ export default {
|
||||
|
||||
showDeleteModal () {
|
||||
this.$root.$emit('bv::show::modal', 'delete-modal')
|
||||
},
|
||||
|
||||
showAddSource () {
|
||||
this.$bvModal.show('add-source-modal')
|
||||
},
|
||||
|
||||
handleSource (bvModalEvt) {
|
||||
// Prevent modal from closing
|
||||
bvModalEvt.preventDefault()
|
||||
// Trigger submit handler
|
||||
this.addSource()
|
||||
},
|
||||
|
||||
addSource () {
|
||||
const list = this.playlist
|
||||
list.push(this.newSource)
|
||||
this.$store.commit('playlist/UPDATE_PLAYLIST', this.$processPlaylist(this.startInSec, list))
|
||||
|
||||
this.newSource = {
|
||||
begin: 0,
|
||||
in: 0,
|
||||
out: 0,
|
||||
duration: 0,
|
||||
category: '',
|
||||
source: ''
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.$bvModal.hide('add-source-modal')
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ export const mutations = {
|
||||
}
|
||||
|
||||
export const actions = {
|
||||
async getTree ({ commit, dispatch, state, rootState }, { extensions, path }) {
|
||||
async getTree ({ commit, dispatch, state, rootState }, { path }) {
|
||||
const crumbs = []
|
||||
let root = '/'
|
||||
const channel = rootState.config.configGui[rootState.config.configID].id
|
||||
|